Pokemon Stock Monitor - Initial commit
Chrome extension for PokemonCenter monitoring with Discord notifications. Includes Python scripts for Target monitoring. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
// 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...");
|
||||
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 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;
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user