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