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:
2026-03-24 12:39:09 -04:00
commit 9d49a99916
18 changed files with 2653 additions and 0 deletions
+109
View File
@@ -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;
}
})();