2da4e1856f
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
146 lines
5.1 KiB
JavaScript
146 lines
5.1 KiB
JavaScript
// Pokemon Stock Monitor - Content Script
|
|
// Runs on PokemonCenter pages to extract product data
|
|
|
|
(function() {
|
|
console.log("[Pokemon Monitor] Content script loaded on:", window.location.href);
|
|
|
|
// Listen for messages from background script
|
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
if (message.type === "extractProducts") {
|
|
console.log("[Pokemon Monitor] Extracting products...");
|
|
debugPageContent();
|
|
const products = extractProducts();
|
|
console.log("[Pokemon Monitor] Found", products.length, "products");
|
|
sendResponse(products);
|
|
}
|
|
return true;
|
|
});
|
|
|
|
// Debug function to see what's on the page
|
|
function debugPageContent() {
|
|
console.log("[Pokemon Monitor] === PAGE DEBUG ===");
|
|
console.log("[Pokemon Monitor] Title:", document.title);
|
|
console.log("[Pokemon Monitor] Body text length:", document.body?.textContent?.length || 0);
|
|
|
|
// Check for bot protection indicators
|
|
const bodyText = document.body?.textContent?.toLowerCase() || "";
|
|
if (bodyText.includes("verify") || bodyText.includes("robot") || bodyText.includes("captcha")) {
|
|
console.log("[Pokemon Monitor] WARNING: Possible bot protection detected!");
|
|
}
|
|
|
|
// Count all links
|
|
const allLinks = document.querySelectorAll('a');
|
|
console.log("[Pokemon Monitor] Total links on page:", allLinks.length);
|
|
|
|
// Check for product links with various patterns
|
|
const productLinks = document.querySelectorAll('a[href*="/product/"]');
|
|
console.log("[Pokemon Monitor] Links with /product/:", productLinks.length);
|
|
|
|
// Try alternative selectors
|
|
const cardLinks = document.querySelectorAll('[class*="product"] a, [class*="card"] a, [class*="tile"] a');
|
|
console.log("[Pokemon Monitor] Links in product/card/tile containers:", cardLinks.length);
|
|
|
|
// Sample some hrefs to understand page structure
|
|
const sampleHrefs = Array.from(allLinks).slice(0, 10).map(a => a.href);
|
|
console.log("[Pokemon Monitor] Sample hrefs:", sampleHrefs);
|
|
|
|
// Look for any elements that might be products
|
|
const possibleProducts = document.querySelectorAll('[class*="product"], [class*="card"], [data-product], [data-item]');
|
|
console.log("[Pokemon Monitor] Elements with product/card classes:", possibleProducts.length);
|
|
|
|
console.log("[Pokemon Monitor] === END DEBUG ===");
|
|
}
|
|
|
|
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 text = link.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();
|
|
// 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;
|
|
}
|
|
})();
|