Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 150 additions & 83 deletions MCSA.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import sys
import time
import smtplib
from datetime import datetime
from dotenv import load_dotenv
from selenium import webdriver
from email.mime.text import MIMEText
Expand All @@ -10,30 +11,62 @@
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager

# Configuration
STORE_ID = '185' # Store ID (185 = Miami, 131 = Dallas)
PRODUCT_URL = "https://www.microcenter.com/product/690450/NVIDIA_GeForce_RTX_5090_Windforce_Overclocked_Triple_Fan_32GB_GDDR7_PCIe_50_Graphics_Card"
CHECK_INTERVAL = 60 # Seconds between stock checks


def get_timestamp():
"""Return current timestamp string"""
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")


def wait_for_cloudflare(driver, timeout=60):
"""Wait for Cloudflare challenge to complete, return True if passed"""
start = time.time()
while time.time() - start < timeout:
page_source = driver.page_source
if "Just a moment" not in page_source and "Verifying you are human" not in page_source:
elapsed = int(time.time() - start)
if elapsed > 0:
print(f" Cloudflare passed after {elapsed}s")
return True
print(f" [{get_timestamp()}] Waiting for Cloudflare... ({int(time.time() - start)}s)")
time.sleep(3)
print(f" [{get_timestamp()}] Cloudflare timeout after {timeout}s")
return False


def set_store_cookie(driver):
# Get the main store page URL so you don't overload the product webpage. Selenium limitation
"""Load Microcenter and set store cookie, return True if successful"""
print(f"[{get_timestamp()}] Loading microcenter.com...")
driver.get("https://www.microcenter.com")

# Wait for the page to load, adjust as needed (seconds)
time.sleep(0)
if not wait_for_cloudflare(driver):
return False

# Set the storeSelected cookie. Default: 131 (Dallas store)
# Set the store cookie
driver.add_cookie({
'name': 'storeSelected',
'value': '131',
'value': STORE_ID,
'domain': '.microcenter.com',
'path': '/',
'secure': True,
'httpOnly': False
})
# Get the product page URL
driver.get("https://www.microcenter.com/product/687907/amd-ryzen-7-9800x3d-granite-ridge-am5-470ghz-8-core-boxed-processor-heatsink-not-included")

# Wait for the page to load, adjust as needed (seconds)
time.sleep(0)
print(f"[{get_timestamp()}] Loading product page...")
driver.get(PRODUCT_URL)

if not wait_for_cloudflare(driver):
return False

return True


def prompt():
# Prompts the user for email and password securely
"""Prompt user for email credentials"""
print("Security Disclaimer:\n"
"Your personal information can only be accessed by a system administrator while the program is running.\n"
"This information cannot be accessed by outside users and is immediately deleted upon the end of program runtime.\n"
Expand All @@ -44,112 +77,146 @@ def prompt():
"In the 'App name' field enter any title specific to this program.\n"
"Copy and paste the newly generated password into the appropriate field below:")

# Set the environment variables
os.environ["email"] = input("Enter your email: ")
os.environ["password"] = input("Enter your password: ")

def test_email():
# Send the test email

def send_email(subject, body):
"""Send email notification"""
try:
# Get the environment variables
email_user = os.getenv("email")
email_password = os.getenv("password")

# Confirm whether email credentials were entered
if not email_user or not email_password:
raise Exception("Test email credentials not found")
raise Exception("Email credentials not found")

# Set up the email
msg = MIMEMultipart()
msg['From'] = email_user
msg['To'] = email_user
msg['Subject'] = 'Test Email Successful'
msg.attach(MIMEText('This is a test email to confirm that your email login is correctly working alongside SMTP within the Microcenter Stock Python Application.\n\n'
'You may now let the application run in the background while it actively checks stock of your selected Microcenter product.','plain'))
msg['Subject'] = subject
msg.attach(MIMEText(body, 'plain'))

# Connect to the email server and send the email
with smtplib.SMTP("smtp.gmail.com", 587) as server:
server.starttls()
server.login(email_user, email_password)
server.sendmail(email_user, email_user, msg.as_string())

print("Test email sent successfully!")
print(f"[{get_timestamp()}] Email sent successfully!")
return True

# Test email exception
except Exception as e:
print(f"Failed to send test email: {e}")
print("Application will continue without user email")
print(f"[{get_timestamp()}] Failed to send email: {e}")
return False

def send_email():
try:
# Get the environment variables
email_user = os.getenv("email")
email_password = os.getenv("password")

# Confirm whether email credentials were entered
if not email_user or not email_password:
raise Exception("Email credentials not found")

# Set up the email
msg = MIMEMultipart()
msg['From'] = email_user
msg['To'] = email_user
msg['Subject'] = 'Your Microcenter Product is Now In Stock'
msg.attach(MIMEText('Your product is in stock. Reserve it online or head to the store to get it while supplies last.','plain'))

# Connect to the email server and send the email
with smtplib.SMTP("smtp.gmail.com", 587) as server:
server.starttls()
server.login(email_user, email_password)
server.sendmail(email_user, email_user, msg.as_string())

print("Email sent successfully!")
def test_email():
"""Send test email to verify credentials"""
print(f"[{get_timestamp()}] Sending test email...")
success = send_email(
'StockSmart - Test Email Successful',
'This is a test email to confirm that your email login is correctly working.\n\n'
'You may now let the application run in the background while it actively checks stock.'
)
if not success:
print("Application will continue without email notifications")

# Test email exception
except Exception as e:
print(f"Failed to send email: {e}")

def check_stock(driver):
# Set the store cookie to Dallas (storeSelected: 131)
set_store_cookie(driver)
"""Check stock status and send notification if in stock. Returns True if page loaded successfully."""
if not set_store_cookie(driver):
print(f"[{get_timestamp()}] Failed to load page (Cloudflare block)")
return False

# Get the page source (HTML)
page_source = driver.page_source

# Search for 'inStock': 'False' in the page source
if "'inStock':'True'" in page_source:
print("In Stock!")
send_email()
driver.quit() # Close the browser
sys.exit()
print(f"[{get_timestamp()}] ✓ IN STOCK!")
send_email(
'StockSmart - Your Product is IN STOCK!',
f'Your product is in stock at Microcenter!\n\n'
f'Reserve it online or head to the store to get it while supplies last.\n\n'
f'Click here to view/reserve:\n{PRODUCT_URL}\n\n'
f'Store ID: {STORE_ID}'
)
return "IN_STOCK"
else:
print("Out of Stock!")
print(f"[{get_timestamp()}] ✗ Out of Stock")
return True


def create_driver():
"""Create and configure Chrome WebDriver with stealth options"""
chrome_options = Options()
chrome_options.add_argument("--headless=new")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--log-level=3")
chrome_options.add_argument("--disable-logging")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--disable-software-rasterizer")

# Stealth options to bypass bot detection
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
chrome_options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
chrome_options.add_argument("--window-size=1920,1080")
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation", "enable-logging"])
chrome_options.add_experimental_option('useAutomationExtension', False)

service = Service(ChromeDriverManager().install())
service.creation_flags = 0x08000000 # CREATE_NO_WINDOW flag on Windows

driver = webdriver.Chrome(service=service, options=chrome_options)
driver.set_page_load_timeout(60)

# Remove webdriver flag to appear more like a real browser
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
})

return driver


def main():
# Enter email and password
# Setup
prompt()

# Specify the config.env path
config_path = ".venv/config.env"

# Load email credentials from the config.env file
load_dotenv(config_path)

# Test email
load_dotenv(".venv/config.env")
test_email()

while True:
# Set up Chrome options to enable headless mode
chrome_options = Options()
chrome_options.add_argument("--headless") # Enable headless mode
chrome_options.add_argument("--disable-gpu") # Disable GPU acceleration
chrome_options.add_argument("--no-sandbox") # Fix potential environment issues

# Set up the WebDriver with the specified options
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)
print(f"\n[{get_timestamp()}] Starting stock checker...")
print(f"[{get_timestamp()}] Checking every {CHECK_INTERVAL} seconds")
print(f"[{get_timestamp()}] Store ID: {STORE_ID}")
print("-" * 50)

# Call the function to check stock
check_stock(driver) # Check stock status once
time.sleep(300) # Change the number to check stock sooner/faster (seconds). Default: checks stock every ~5 minutes

main()
try:
while True:
driver = None
try:
driver = create_driver()
result = check_stock(driver)

if result == "IN_STOCK":
driver.quit()
print(f"\n[{get_timestamp()}] Stock found! Exiting...")
sys.exit(0)

except KeyboardInterrupt:
raise # Re-raise to exit the outer try block
except Exception as e:
print(f"[{get_timestamp()}] ⚠ Error: {e}")
finally:
if driver:
try:
driver.quit()
except Exception:
pass

print(f"[{get_timestamp()}] Next check in {CHECK_INTERVAL}s...")
time.sleep(CHECK_INTERVAL)

except KeyboardInterrupt:
print(f"\n[{get_timestamp()}] Stopped by user (Ctrl+C)")
sys.exit(0)


if __name__ == "__main__":
main()
Loading