Files
pokemon-stock-checker/main.py
T
mmcghen 9d49a99916 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>
2026-03-24 12:39:09 -04:00

233 lines
7.2 KiB
Python

"""
Pokemon Stock Monitor - Main entry point
Monitors retail sites for Pokemon card restocks and new drops
"""
import logging
import time
import signal
import sys
from datetime import datetime
import schedule
from config import (
CHECK_INTERVAL_SECONDS,
SITES_ENABLED,
POKEMON_CENTER_URLS,
TARGET_URLS,
KEYWORD_FILTER_ENABLED,
KEYWORDS,
LOG_LEVEL,
)
from browser import get_browser, shutdown_browser
from product_tracker import ProductTracker
from discord_notifier import send_stock_alert, send_startup_notification, send_error_notification
from scrapers import PokemonCenterScraper, TargetScraper
# Setup logging
logging.basicConfig(
level=getattr(logging, LOG_LEVEL),
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.StreamHandler(),
logging.FileHandler("monitor.log", encoding="utf-8"),
],
)
logger = logging.getLogger(__name__)
# Global tracker
tracker = ProductTracker()
# Scrapers
scrapers = {
"pokemoncenter": PokemonCenterScraper(),
"target": TargetScraper(),
}
def check_pokemoncenter():
"""Check PokemonCenter for restocks and new drops"""
logger.info("Checking PokemonCenter...")
scraper = scrapers["pokemoncenter"]
try:
for url in POKEMON_CENTER_URLS:
logger.info(f"Scraping: {url}")
products = scraper.scrape_category_page(url)
# Apply keyword filter if enabled
if KEYWORD_FILTER_ENABLED and KEYWORDS:
products = scraper.filter_by_keywords(products, KEYWORDS)
logger.info(f"After keyword filter: {len(products)} products")
# Process and detect changes
new_products, restocked_products = tracker.process_products(products)
# Send notifications for new products
for product in new_products:
if product.in_stock:
send_stock_alert(
product_name=product.name,
product_url=product.url,
price=product.price or "See link",
site="pokemoncenter",
alert_type="new_drop",
image_url=product.image_url,
)
logger.info(f"Sent notification for new product: {product.name}")
# Send notifications for restocks
for product in restocked_products:
send_stock_alert(
product_name=product.name,
product_url=product.url,
price=product.price or "See link",
site="pokemoncenter",
alert_type="restock",
image_url=product.image_url,
)
logger.info(f"Sent notification for restock: {product.name}")
# Log stats
stats = tracker.get_stats()
logger.info(
f"Stats: {stats['total_products']} total, "
f"{stats['in_stock']} in stock, "
f"{stats['out_of_stock']} out of stock"
)
except Exception as e:
logger.error(f"Error checking PokemonCenter: {e}")
send_error_notification(f"Error checking PokemonCenter: {str(e)}", "PokemonCenter")
def check_target():
"""Check Target for restocks and new drops"""
logger.info("Checking Target...")
scraper = scrapers["target"]
try:
for url in TARGET_URLS:
logger.info(f"Scraping: {url}")
products = scraper.scrape_category_page(url)
# Apply keyword filter if enabled
if KEYWORD_FILTER_ENABLED and KEYWORDS:
products = scraper.filter_by_keywords(products, KEYWORDS)
logger.info(f"After keyword filter: {len(products)} products")
# Process and detect changes
new_products, restocked_products = tracker.process_products(products)
# Send notifications for new products
for product in new_products:
if product.in_stock:
send_stock_alert(
product_name=product.name,
product_url=product.url,
price=product.price or "See link",
site="target",
alert_type="new_drop",
image_url=product.image_url,
)
logger.info(f"Sent notification for new product: {product.name}")
# Send notifications for restocks
for product in restocked_products:
send_stock_alert(
product_name=product.name,
product_url=product.url,
price=product.price or "See link",
site="target",
alert_type="restock",
image_url=product.image_url,
)
logger.info(f"Sent notification for restock: {product.name}")
# Log stats
stats = tracker.get_stats()
logger.info(
f"Stats: {stats['total_products']} total, "
f"{stats['in_stock']} in stock, "
f"{stats['out_of_stock']} out of stock"
)
except Exception as e:
logger.error(f"Error checking Target: {e}")
send_error_notification(f"Error checking Target: {str(e)}", "Target")
def run_checks():
"""Run all enabled site checks"""
logger.info(f"Running checks at {datetime.now().strftime('%H:%M:%S')}")
if "pokemoncenter" in SITES_ENABLED:
check_pokemoncenter()
if "target" in SITES_ENABLED:
check_target()
logger.info("Check cycle complete")
def graceful_shutdown(signum, frame):
"""Handle shutdown gracefully"""
logger.info("Shutting down...")
shutdown_browser()
sys.exit(0)
def main():
"""Main entry point"""
print("=" * 50)
print("Pokemon Stock Monitor")
print("=" * 50)
print(f"Monitoring: {', '.join(SITES_ENABLED)}")
print(f"Check interval: {CHECK_INTERVAL_SECONDS} seconds")
print(f"Keyword filter: {'ON' if KEYWORD_FILTER_ENABLED else 'OFF'}")
print("=" * 50)
print()
# Register signal handlers
signal.signal(signal.SIGINT, graceful_shutdown)
signal.signal(signal.SIGTERM, graceful_shutdown)
# Initialize browser
logger.info("Initializing browser...")
try:
get_browser()
except Exception as e:
logger.error(f"Failed to initialize browser: {e}")
print(f"\nERROR: Failed to start browser. Make sure Playwright is installed:")
print(" pip install playwright")
print(" playwright install chromium")
return
# Send startup notification
send_startup_notification()
# Run initial check
print("Running initial check...")
run_checks()
# Schedule periodic checks
schedule.every(CHECK_INTERVAL_SECONDS).seconds.do(run_checks)
print(f"\nMonitor running! Checking every {CHECK_INTERVAL_SECONDS} seconds.")
print("Press Ctrl+C to stop.\n")
# Main loop
try:
while True:
schedule.run_pending()
time.sleep(1)
except KeyboardInterrupt:
pass
finally:
graceful_shutdown(None, None)
if __name__ == "__main__":
main()