diff --git a/MCSA.py b/MCSA.py
index 0a1cf5d..321dc11 100644
--- a/MCSA.py
+++ b/MCSA.py
@@ -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
@@ -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"
@@ -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()
\ No newline at end of file
+ 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()
diff --git a/README.md b/README.md
index fb45816..fcf0b14 100644
--- a/README.md
+++ b/README.md
@@ -1,106 +1,106 @@
-# Micro Center Stock Application (MCSA) v1.0.1
+# Micro Center Stock Application (MCSA) v2.0.0
-###### Licensed with Apache 2.0 - Check the LICENSE file in the main branch for more information
+###### *Licensed with Apache 2.0 - Check the **LICENSE** file in the main branch for more information*
Check the stock of a specific Micro Center product. Optionally, securely link your email to get notified when your selected product is in stock.
-## Installation
+## What's New in v2.0.0
-**Note: The default code looks for an AMD Ryzen 7 9800X3D at a Micro Center Store in Dallas, TX
+- **Cloudflare Bypass** - Automatic handling of Cloudflare bot protection
+- **Stealth Mode** - Chrome stealth options to avoid detection
+- **Better Logging** - Timestamps and status messages for all operations
+- **Configuration at Top** - Easy-to-edit config variables (no more hunting for line numbers)
+- **Graceful Exit** - Ctrl+C properly stops the program
+- **Improved Email** - Notifications include direct product link
+## Installation
-Step-by-step instructions for setting up the project:
-
-1. Clone the repository:
+**Note:** The default code monitors an NVIDIA RTX 5090 at Miami Micro Center
+
+### Step-by-step instructions:
+
+1. **Clone the repository:**
```bash
git clone https://github.com/ayoTyler/Micro-Center-Stock-Application.git
```
- *Alternatively: If you choose to download the .zip from GitHub:
- 1\. Extract the files to a folder using 7zip or WinRAR
- 2\. Open the MCSA.py application in a Python-supported IDE. (I recommend PyCharm Community Edition)
-
-2. Open the command console/terminal window within your IDE. Change directory (cd) to the project folder's location (if needed) and run the following:
+
+2. **Install dependencies:**
```bash
+ cd Micro-Center-Stock-Application
pip install -r requirements.txt
```
- *Alternatively: To use your system's command console/terminal window to install the items listed within the requirements folder:
- 1\. Change directory (cd) to the project folder's location that contains requirements.txt
- 2\. Run the above command
-
-3. On Line 23 within the code, replace the Store Identification Number with your local Micro Center Store Identification Number:
- ```python
- # Line 23
- 'value': 'store number here',
- ```
-
- To find your local Micro Center Store Number:
- 1. Make sure you have selected a store on www.microcenter.com
- 2. Right-Click the page, choose 'Inspect'
- 3. Click the double arrow (>>) at the top of the Inspect Element console and select 'Application'
- 4. Under 'Storage', click 'Cookies'
- 5. Under the 'Name' column, look for 'storeSelected' and find the appropriate value associated with that cookie.
- 6. Replace the number on Line 23 in the code with the appropriate Store Identification Number as shown in the example below.
-
- ```python
- # Example
- # Line 23
- 'value': '131',
- ```
-
-4. On Line 30 within the code, replace the Product Page URL with your desired product:
- ```python
- # Line 30 - Get the product page URL
- driver.get("https://www.microcenter.com/your-product-url-here")
- ```
-
-5. (Optional) Adjust the speed that the program functions:
-
-
- Slower Internet = Higher Number (~30)
+
+3. **Configure the script** - Edit the top of `MCSA.py`:
```python
- # Line 18 & Line 33 - Wait for the page to load,
- # adjust as needed (seconds)
- time.sleep(30)
+ # Configuration
+ STORE_ID = '185' # Your Micro Center store ID
+ PRODUCT_URL = "https://www.microcenter.com/product/..." # Product you want
+ CHECK_INTERVAL = 60 # Seconds between checks
```
-
-
-
- Faster Internet = Lower Number (~5)
- ```python
- # Line 18 & Line 33 - Wait for the page to load,
- # adjust as needed (seconds)
- time.sleep(5)
+
+### Finding Your Store ID
+
+1. Go to www.microcenter.com and select your store
+2. Right-click the page → 'Inspect'
+3. Click the >> arrows → select 'Application'
+4. Under 'Storage' → 'Cookies'
+5. Find `storeSelected` - that's your store ID
+
+**Common Store IDs:**
+| Store | ID |
+|-------|-----|
+| Dallas | 131 |
+| Miami | 185 |
+| Houston | 155 |
+
+4. **Run the script:**
+ ```bash
+ python MCSA.py
```
-
-
- It is NOT recommended to set the number below 5.
-
-
-6. (Optional) When you run the program, follow the instructions in the terminal/run console to securely connect your email to the application.
-
- When linked, this application sends you an alert email when your desired product is in stock. Otherwise, the program will run normally and notify you via the terminal/run console.
-
- To securely link your email, follow the instructions in the terminal/run console or the instructions below:
- 1\. Go to myaccount.google.com
- 2\. Search 'App Passwords' and click the respective option
- 3\. In the 'App name' field, enter any title specific to this program
- 4\. Copy and paste the newly generated password into the appropriate fields given to you in the terminal/run console
- 5\. Enter your email
- 6\. Enter your password
- 7\. Expect to receive a test email from yourself confirming that your email is linked with the application
-
-That's all! Leave the program running in the background. If you shut down the computer, be sure to run through the steps again to get it working again.
-
-### Future Update Plans
-
-1. Show and email the amount in stock
-2. GUI window
-3. Multiple Micro Center stores listed - no code-changing required
-4. Works for other websites
-
-
-### If this program helped you, buy me a coffee :)
-
-https://buymeacoffee.com/ayotyler < - - - - - - - - - - - - - - - - -
-
-
\ No newline at end of file
+
+5. **(Optional) Email Notifications:**
+
+ Follow the prompts to link your Gmail for stock alerts:
+ 1. Go to myaccount.google.com
+ 2. Search 'App Passwords' and click it
+ 3. Create a new app password for this program
+ 4. Enter your email and the generated password when prompted
+
+## Usage
+
+```
+[2025-12-26 02:30:00] Starting stock checker...
+[2025-12-26 02:30:00] Checking every 60 seconds
+[2025-12-26 02:30:00] Store ID: 185
+--------------------------------------------------
+[2025-12-26 02:30:01] Loading microcenter.com...
+[2025-12-26 02:30:02] Loading product page...
+[2025-12-26 02:30:03] ✗ Out of Stock
+[2025-12-26 02:30:03] Next check in 60s...
+```
+
+Press `Ctrl+C` to stop the program.
+
+## Troubleshooting
+
+### Cloudflare Blocking
+If you see "Waiting for Cloudflare..." messages timing out:
+- Try using a VPN with a US server
+- Cloudflare may be stricter for non-US connections
+
+### Email Not Sending
+- Make sure you're using an **App Password**, not your regular Gmail password
+- Enable 2FA on your Google account if not already
+
+## Future Update Plans
+
+1. Show and email the amount in stock
+2. GUI window
+3. Multiple Micro Center stores - no code-changing required
+4. Support for other websites
+
+## Support
+
+If this program helped you, buy me a coffee :)
+
+https://buymeacoffee.com/ayotyler