diff --git a/.github/workflows/app_linuxBuild.yml b/.github/workflows/app_linuxBuild.yml
index 130db9a..8027b6c 100644
--- a/.github/workflows/app_linuxBuild.yml
+++ b/.github/workflows/app_linuxBuild.yml
@@ -2,9 +2,9 @@ name: ShopPyBot Linux
on:
push:
- branches: [master,dev]
+ branches: [master, dev]
pull_request:
- branches: [master]
+ branches: [master, dev]
jobs:
build-linux:
@@ -26,6 +26,6 @@ jobs:
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- # - name: Test with pytest
- # run: |
- # pytest
\ No newline at end of file
+ - name: Test with pytest
+ run: |
+ pytest
\ No newline at end of file
diff --git a/.github/workflows/app_macBuild.yml b/.github/workflows/app_macBuild.yml
index b1dbbf2..6d559ea 100644
--- a/.github/workflows/app_macBuild.yml
+++ b/.github/workflows/app_macBuild.yml
@@ -2,9 +2,9 @@ name: ShopPyBot Mac
on:
push:
- branches: [master,dev]
+ branches: [master, dev]
pull_request:
- branches: [master]
+ branches: [master, dev]
jobs:
build-mac:
@@ -26,6 +26,6 @@ jobs:
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- # - name: Test with pytest
- # run: |
- # pytest
\ No newline at end of file
+ - name: Test with pytest
+ run: |
+ pytest
\ No newline at end of file
diff --git a/.github/workflows/app_windowsBuild.yml b/.github/workflows/app_windowsBuild.yml
index 3658d8a..b798b04 100644
--- a/.github/workflows/app_windowsBuild.yml
+++ b/.github/workflows/app_windowsBuild.yml
@@ -2,9 +2,9 @@ name: ShopPyBot Windows
on:
push:
- branches: [master,dev]
+ branches: [master, dev]
pull_request:
- branches: [master]
+ branches: [master, dev]
jobs:
build-windows:
@@ -26,7 +26,6 @@ jobs:
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- # - name: Test with pytest
- # run: |
- # pytest
-
\ No newline at end of file
+ - name: Test with pytest
+ run: |
+ pytest
\ No newline at end of file
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 1db188b..04edc42 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -2,10 +2,9 @@ name: "CodeQL"
on:
push:
- branches: [ master,dev ]
+ branches: [ master, dev ]
pull_request:
- # The branches below must be a subset of the branches above
- branches: [ master ]
+ branches: [ master, dev ]
schedule:
- cron: '23 5 * * 5'
@@ -23,37 +22,17 @@ jobs:
matrix:
language: [ 'python' ]
-
steps:
- name: Checkout repository
uses: actions/checkout@v2
- # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
- # If you wish to specify custom queries, you can do so here or in a config file.
- # By default, queries listed here will override any specified in a config file.
- # Prefix the list here with "+" to use these queries and those in the config file.
- # queries: ./path/to/local/query, your-org/your-repo/queries@main
- # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
- # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
- # âšī¸ Command-line programs to run using the OS shell.
- # đ https://git.io/JvXDl
-
- # âī¸ If the Autobuild fails above, remove it and uncomment the following three lines
- # and modify them (or add more) to build your code if your project
- # uses a compiled language
-
- #- run: |
- # make bootstrap
- # make release
-
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
-
diff --git a/.gitignore b/.gitignore
index b0ced71..f4e4197 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,172 @@
dev*
-/local/&
-*.exe
\ No newline at end of file
+*local*
+logs/*
+*.exe
+.venv*
+.DS_Store
+config.yml
+data/*
+pytest-cache*
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
+.pdm.toml
+.pdm-python
+.pdm-build/
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
diff --git a/bot-availCheck.py b/_deprecated/bot-availCheck.py
similarity index 100%
rename from bot-availCheck.py
rename to _deprecated/bot-availCheck.py
diff --git a/bot.py b/_deprecated/bot.py
similarity index 99%
rename from bot.py
rename to _deprecated/bot.py
index 4205512..8b4f83a 100644
--- a/bot.py
+++ b/_deprecated/bot.py
@@ -33,6 +33,7 @@ def writeLog(message, type,_loggingLevel=0):
print(bcolors.WARNING,"WARNING:",message,bcolors.ENDC)
elif type.upper() == "INFO" and _loggingLevel >= 3:
print("\033[1;37;40mINFO:",message,bcolors.ENDC)
+
def bbBuy(_driver,_link,_alertSound,_timeout,_queueExists,_email,_pwd,_sec,_testMode,_loggingLevel=0):
_driver.get(_link)
@@ -104,6 +105,7 @@ def bbBuy(_driver,_link,_alertSound,_timeout,_queueExists,_email,_pwd,_sec,_test
if(_alertSound and _alertSound != ""):
playsound(_alertSound,False)
writeLog("YOU'RE IN QUEUE - GOOD LUCK","ALWAYS",_loggingLevel)
+ input("Press enter when finished in the Chrome browser")
return True
def amzSignIn(_driver,_timeout,_email,_pwd,_loggingLevel=0):
diff --git a/installDependencies.ps1 b/_deprecated/installDependencies.ps1
similarity index 100%
rename from installDependencies.ps1
rename to _deprecated/installDependencies.ps1
diff --git a/linkProcessor/data.csv b/_deprecated/linkProcessor/data.csv
similarity index 100%
rename from linkProcessor/data.csv
rename to _deprecated/linkProcessor/data.csv
diff --git a/linkProcessor/out.json b/_deprecated/linkProcessor/out.json
similarity index 100%
rename from linkProcessor/out.json
rename to _deprecated/linkProcessor/out.json
diff --git a/linkProcessor/processor.ps1 b/_deprecated/linkProcessor/processor.ps1
similarity index 100%
rename from linkProcessor/processor.ps1
rename to _deprecated/linkProcessor/processor.ps1
diff --git a/settings.json b/_deprecated/settings.json
similarity index 100%
rename from settings.json
rename to _deprecated/settings.json
diff --git a/activate.ps1 b/activate.ps1
new file mode 100644
index 0000000..60e9c94
--- /dev/null
+++ b/activate.ps1
@@ -0,0 +1,24 @@
+ function Test-Package {
+ param (
+ [string]$packageName
+ )
+ $package = pip show $packageName 2>&1
+ return -not ($package -match "WARNING: Package(s) not found")
+ }
+
+if (-not (Test-Path "$PSScriptRoot\.venv")) {
+ Write-Host "No virtual environment found in $PSScriptRoot\.venv"
+ python -m venv "$PSScriptRoot\.venv"
+ }
+
+ .\.venv\Scripts\activate
+
+ $requirements = Get-Content "$PSScriptRoot\requirements.txt"
+ foreach ($requirement in $requirements) {
+ if (-not (Test-Package -packageName $requirement)) {
+ Write-Host "Installing $requirement..."
+ pip install $requirement
+ } else {
+ Write-Host "$requirement is already installed."
+ }
+ }
\ No newline at end of file
diff --git a/amazon_bot.py b/amazon_bot.py
new file mode 100644
index 0000000..6b20b1a
--- /dev/null
+++ b/amazon_bot.py
@@ -0,0 +1,192 @@
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.webdriver.support import expected_conditions as EC
+from logger import writeLog
+import time
+from utils import play_notification_sound
+from models import update_item_purchased
+
+def detect_captcha(driver):
+ try:
+ WebDriverWait(driver, 5).until(
+ EC.presence_of_element_located((By.XPATH, "//h4[contains(text(), 'Enter the characters you see below')]"))
+ )
+ return True
+ except:
+ return False
+
+def check_amazon_item(driver, item_url):
+ writeLog(f"Entering check_amazon_item for URL: {item_url}", "DEBUG")
+ try:
+ if driver.current_url != item_url:
+ driver.get(item_url)
+ if detect_captcha(driver):
+ writeLog("CAPTCHA detected. Please solve it manually.", "WARNING")
+ driver.focus()
+ input("--------------------\nPress Enter after solving the CAPTCHA...--------------------\n")
+ writeLog("Waiting for add-to-cart or buy-now button", "DEBUG")
+ try:
+ add_to_cart_button = WebDriverWait(driver, 10).until(
+ EC.presence_of_element_located((By.ID, "add-to-cart-button"))
+ )
+ except:
+ writeLog("Could not find Add-to-cart button - assume unavailable", "WARNING")
+ add_to_cart_button = None
+ try:
+ buy_now_button = WebDriverWait(driver, 10).until(
+ EC.presence_of_element_located((By.ID, "buy-now-button"))
+ )
+ except:
+ writeLog("Could not find Buy-now button - assume unavailable", "WARNING")
+ buy_now_button = None
+
+ if add_to_cart_button or buy_now_button:
+ writeLog("Add-to-cart or Buy-now button found", "INFO")
+ writeLog("Item is available on Amazon", "SUCCESS")
+ return True
+ else:
+ writeLog("Add-to-cart or Buy-now button not found", "INFO")
+ writeLog("Item is not available on Amazon", "INFO")
+ return False
+ except Exception as e:
+ writeLog(f"Error checking Amazon item: {e}", "ERROR")
+ return False
+
+def amz_sign_in(driver, config):
+ try:
+ email = config['app']['amz_email']
+ password = config['app']['amz_pwd']
+
+ # Check if the user is already signed in
+ writeLog("Checking if user is already signed in", "INFO")
+ try:
+ account_element = WebDriverWait(driver, 10).until(
+ EC.presence_of_element_located((By.ID, "nav-link-accountList"))
+ )
+ sign_in_button = account_element.find_element(By.CLASS_NAME, "nav-action-signin-button")
+ if sign_in_button:
+ writeLog("User is not signed in", "INFO")
+ else:
+ writeLog("User is already signed in", "INFO")
+ return
+ except Exception as e:
+ writeLog(f"Error checking sign-in state: {e}", "ERROR")
+
+ # User is not signed in, proceed with sign-in
+ writeLog("User is not signed in, proceeding with sign-in", "INFO")
+ driver.get("https://www.amazon.com/ap/signin?openid.pape.max_auth_age=0&openid.return_to=https%3A%2F%2Fwww.amazon.com%2F%3Fref_%3Dnav_signin&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.assoc_handle=usflex&openid.mode=checkid_setup&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0")
+ try:
+ WebDriverWait(driver, 10).until(
+ EC.presence_of_element_located((By.ID, "ap_email"))
+ ).send_keys(email)
+ driver.find_element(By.ID, "continue").click()
+ except Exception as e:
+ writeLog(f"Error during Amazon sign-in when entering email: {e}", "ERROR")
+ raise Exception("Sign-in failed - failed to enter email")
+
+ play_notification_sound()
+ input("Press enter once you dismiss the passkey prompt...")
+
+ try:
+ writeLog("Attempting to enter password", "INFO")
+ WebDriverWait(driver, 10).until(
+ EC.presence_of_element_located((By.ID, "ap_password"))
+ ).send_keys(password)
+ except Exception as e:
+ writeLog(f"Error during Amazon sign-in when entering password: {e}", "ERROR")
+ raise Exception("Sign-in failed - failed to enter password")
+
+ try:
+ writeLog("Attempting to click sign-in button", "INFO")
+ driver.find_element(By.ID, "signInSubmit").click()
+ except Exception as e:
+ writeLog(f"Error during Amazon sign-in when clicking sign-in button: {e}", "ERROR")
+ raise Exception("Sign-in failed")
+ # Check for MFA prompt
+ try:
+ writeLog("Checking for MFA prompt", "INFO")
+ mfa_form = WebDriverWait(driver, 10).until(
+ EC.presence_of_element_located((By.ID, "auth-mfa-form"))
+ )
+ if mfa_form:
+ writeLog("MFA prompt detected. Please enter the OTP manually.", "WARNING")
+ play_notification_sound()
+ input("Press Enter after entering the OTP...")
+ except:
+ writeLog("No MFA prompt detected.", "warning")
+
+ writeLog("Signed in to Amazon", "INFO")
+ except Exception as e:
+ writeLog(f"Error during Amazon sign-in: {e}", "ERROR")
+
+def auto_buy_amazon_item(driver, item_url, config, quantity, test_mode=False):
+ writeLog(f"Entering auto_buy_amazon_item for URL: {item_url}", "DEBUG")
+ amz_sign_in(driver, config)
+ try:
+ if driver.current_url != item_url:
+ driver.get(item_url)
+
+ try:
+ writeLog("Attempting to find quantity dropdown", "INFO")
+ start_time = time.time()
+ quantity_dropdown = WebDriverWait(driver, 10).until(
+ EC.presence_of_element_located((By.CLASS_NAME, "a-button-dropdown"))
+ )
+ end_time = time.time()
+ writeLog(f"Time to find quantity dropdown: {end_time - start_time:.2f} seconds", "DEBUG")
+ quantity_dropdown.click()
+ except Exception as e:
+ writeLog(f"Error finding quantity dropdown: {e}", "ERROR")
+ return
+
+ try:
+ writeLog(f"Attempting to find quantity option for {quantity}", "INFO")
+ start_time = time.time()
+ quantity_option = WebDriverWait(driver, 10).until(
+ EC.presence_of_element_located((By.ID, f"quantity_{quantity-1}"))
+ )
+ end_time = time.time()
+ writeLog(f"Time to find quantity option: {end_time - start_time:.2f} seconds", "DEBUG")
+ quantity_option.click()
+ except Exception as e:
+ writeLog(f"Error finding quantity option: {e}", "ERROR")
+ return
+
+ if test_mode:
+ writeLog("Test mode active: Pausing before final purchase step", "DEBUG")
+ input("Press Enter to continue...")
+
+ try:
+ writeLog("Attempting to find buy-now button", "INFO")
+ start_time = time.time()
+ buy_now_button = WebDriverWait(driver, 10).until(
+ EC.presence_of_element_located((By.ID, "buy-now-button"))
+ )
+ end_time = time.time()
+ writeLog(f"Time to find buy-now button: {end_time - start_time:.2f} seconds", "DEBUG")
+ buy_now_button.click()
+ except Exception as e:
+ writeLog(f"Error finding buy-now button: {e}", "ERROR")
+ return
+
+ try:
+ writeLog("Attempting to find place order button", "INFO")
+ start_time = time.time()
+ place_order_button = WebDriverWait(driver, 10).until(
+ EC.presence_of_element_located((By.ID, "submitOrderButtonId"))
+ )
+ end_time = time.time()
+ writeLog(f"Time to find place order button: {end_time - start_time:.2f} seconds", "DEBUG")
+ if not test_mode:
+ place_order_button.click()
+ writeLog("Order placed on Amazon", "SUCCESS")
+ update_item_purchased(item_url)
+ else:
+ writeLog("Test mode active: Skipping final purchase step, otherwise submitOrderButton would have been clicked!", "SUCCESS")
+ input("Press Enter to continue...")
+ except Exception as e:
+ writeLog(f"Error finding place order button: {e}", "ERROR")
+ return
+ except Exception as e:
+ writeLog(f"Error during Amazon auto-buy: {e}", "ERROR")
\ No newline at end of file
diff --git a/bestbuy_bot.py b/bestbuy_bot.py
new file mode 100644
index 0000000..15ef15d
--- /dev/null
+++ b/bestbuy_bot.py
@@ -0,0 +1,73 @@
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.webdriver.support import expected_conditions as EC
+from logger import writeLog
+
+def check_bestbuy_item(driver, item_url):
+ writeLog(f"Entering check_bestbuy_item for URL: {item_url}", "DEBUG")
+ try:
+ driver.get(item_url)
+ writeLog("Waiting for add-to-cart button", "DEBUG")
+ add_to_cart_button = WebDriverWait(driver, 10).until(
+ EC.presence_of_element_located((By.CLASS_NAME, "add-to-cart-button"))
+ )
+ if add_to_cart_button:
+ writeLog("Add-to-cart button found", "INFO")
+ writeLog("Item is available on BestBuy", "SUCCESS")
+ return True
+ else:
+ writeLog("Add-to-cart button not found", "INFO")
+ writeLog("Item is not available on BestBuy", "INFO")
+ return False
+ except Exception as e:
+ writeLog(f"Error checking BestBuy item: {e}", "ERROR")
+ return False
+
+def bb_sign_in(driver, email, password):
+ writeLog("Entering bb_sign_in", "DEBUG")
+ try:
+ driver.get("https://www.bestbuy.com/identity/signin")
+ WebDriverWait(driver, 10).until(
+ EC.presence_of_element_located((By.ID, "fld-e"))
+ ).send_keys(email)
+ driver.find_element(By.ID, "fld-p1").send_keys(password)
+ driver.find_element(By.CLASS_NAME, "cia-form__controls__submit").click()
+ writeLog("Signed in to BestBuy", "INFO")
+ except Exception as e:
+ writeLog(f"Error during BestBuy sign-in: {e}", "ERROR")
+
+def auto_buy_bestbuy_item(driver, item_url, email, password, cvv, quantity):
+ writeLog(f"Entering auto_buy_bestbuy_item for URL: {item_url}", "DEBUG")
+ try:
+ driver.get(item_url)
+ WebDriverWait(driver, 10).until(
+ EC.presence_of_element_located((By.CLASS_NAME, "add-to-cart-button"))
+ ).click()
+ writeLog("Added to cart on BestBuy", "INFO")
+
+ driver.get("https://www.bestbuy.com/cart")
+
+ # Update quantity
+ quantity_dropdown = WebDriverWait(driver, 10).until(
+ EC.presence_of_element_located((By.CLASS_NAME, "a-dropdown-prompt"))
+ )
+ quantity_dropdown.click()
+
+ quantity_option = WebDriverWait(driver, 10).until(
+ EC.presence_of_element_located((By.XPATH, f"//a[@id='quantity_{quantity}']"))
+ )
+ quantity_option.click()
+
+ driver.find_element(By.CLASS_NAME, "checkout-buttons__checkout").click()
+ writeLog("Proceeded to checkout on BestBuy", "INFO")
+
+ bb_sign_in(driver, email, password)
+
+ WebDriverWait(driver, 10).until(
+ EC.presence_of_element_located((By.ID, "credit-card-cvv"))
+ ).send_keys(cvv)
+ driver.find_element(By.CLASS_NAME, "button--place-order").click()
+ writeLog("Order placed on BestBuy", "SUCCESS")
+ except Exception as e:
+ writeLog(f"Error during BestBuy auto-buy: {e}", "ERROR")
\ No newline at end of file
diff --git a/config.py b/config.py
new file mode 100644
index 0000000..f6180f4
--- /dev/null
+++ b/config.py
@@ -0,0 +1,7 @@
+import yaml
+
+def load_config():
+ with open('config.yml', 'r') as file:
+ return yaml.safe_load(file)
+
+config = load_config()
\ No newline at end of file
diff --git a/logger.py b/logger.py
new file mode 100644
index 0000000..acf8bed
--- /dev/null
+++ b/logger.py
@@ -0,0 +1,37 @@
+import logging
+import os
+from datetime import datetime
+from colorama import Fore, Style
+import yaml
+
+def load_settings():
+ with open('config.yml', 'r') as file:
+ return yaml.safe_load(file)
+
+def setup_logger():
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
+ return logging.getLogger(__name__)
+
+def writeLog(message: str, type: str, writeTofile: bool = True) -> None:
+ settings = load_settings()
+ loggingLevel = settings.get('debug', {}).get('logging_level', 5)
+ log_levels = {
+ "ALWAYS": (Fore.CYAN, 0),
+ "ERROR": (Fore.RED, 1),
+ "WARNING": (Fore.YELLOW, 2),
+ "SUCCESS": (Fore.GREEN, 2),
+ "INFO": (Fore.WHITE, 3),
+ "DEBUG": (Fore.BLUE, 4),
+ "TRACE": (Fore.MAGENTA, 5)
+ }
+ color, level = log_levels.get(type.upper(), (Fore.LIGHTBLACK_EX, 0))
+ if loggingLevel >= level:
+ print(f"{color}[{type.upper()}][{datetime.now().strftime('%Y%B%d@%H:%M:%S')}] {message}{Style.RESET_ALL}")
+ if writeTofile:
+ _scriptdir = os.path.dirname(os.path.realpath(__file__))
+ log_dir = os.path.join(_scriptdir, "logs")
+ if not os.path.exists(log_dir):
+ os.makedirs(log_dir)
+ log_file_path = os.path.join(log_dir, f"{datetime.now().strftime('%Y%B%d')}.log")
+ with open(log_file_path, "a", encoding="utf-8") as logFile:
+ logFile.write(f"[{type.upper()}][{datetime.now().strftime('%Y%B%d@%H:%M:%S')}] {message}\n")
\ No newline at end of file
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..13f3ac2
--- /dev/null
+++ b/main.py
@@ -0,0 +1,140 @@
+import sys
+import os
+import yaml
+import requests
+from selenium import webdriver
+from selenium.webdriver.chrome.service import Service
+from selenium.webdriver.chrome.options import Options
+from webdriver_manager.chrome import ChromeDriverManager
+from logger import setup_logger, writeLog
+from amazon_bot import check_amazon_item, auto_buy_amazon_item,detect_captcha
+from bestbuy_bot import check_bestbuy_item, auto_buy_bestbuy_item
+from utils import play_notification_sound, play_buy_sound, play_available_sound
+import webbrowser
+from selenium.webdriver.chrome.options import Options
+from models import initialize_db, add_items, get_items
+from config import config
+
+def get_chromedriver_path():
+ writeLog("Entering get_chromedriver_path", "DEBUG")
+ driver_path = config['selenium']['driver_path']
+ if not os.path.exists(driver_path):
+ writeLog(f"Chromedriver not found at {driver_path}. Trying to download the latest version.", "WARNING")
+ driver_path = ChromeDriverManager().install()
+ if not os.path.exists(driver_path):
+ writeLog("Failed to download the latest Chromedriver. Exiting.", "ERROR")
+ exit(1)
+ writeLog(f"Chromedriver path: {driver_path}", "DEBUG")
+ return driver_path
+
+def make_tiny(url):
+ writeLog(f"Creating tiny URL for {url}", "DEBUG")
+ request_url = f'http://tinyurl.com/api-create.php?url={url}'
+ response = requests.get(request_url)
+ short_url = response.text
+ writeLog(f"Tiny URL created: {short_url}", "DEBUG")
+ return short_url
+
+def load_config():
+ with open('config.yml', 'r') as file:
+ return yaml.safe_load(file)
+
+def main():
+ writeLog("Starting main function", "INFO")
+ config = load_config()
+ driver_path = get_chromedriver_path()
+ service = Service(driver_path)
+
+ # Set up Chrome options
+ chromeOptions = Options()
+ prefs = {
+ "credentials_enable_service": False,
+ "profile.password_manager_enabled": False,
+ "autofill.profile_enabled": False,
+ "autofill.credit_card_enabled": False
+ }
+ chromeOptions.add_experimental_option("prefs", prefs)
+ chromeOptions.add_argument("--disable-blink-features=AutomationControlled")
+ chromeOptions.add_argument("--disable-notifications")
+ chromeOptions.add_argument("--disable-extensions")
+ chromeOptions.add_argument("--disable-web-security")
+ chromeOptions.add_argument("--disable-site-isolation-trials")
+ chromeOptions.add_argument("--disable-infobars")
+ chromeOptions.add_argument("--disable-save-password-bubble")
+ chromeOptions.add_argument("--disable-translate")
+ chromeOptions.add_argument("--disable-features=AutofillServerCommunication,PasswordManagerOnboarding,PasswordManagerSettings,PasswordManagerUI,PasswordManagerInBrowserSettings,PasswordManagerReauthentication,PasswordManagerAccountStorage,PasswordManager,PasswordAutofillPublicSuffixDomainMatching,PasswordAutofill,PasswordGeneration,PasswordImportExport,PasswordLeakDetection,PasswordReuseDetection,PasswordSave")
+
+ # Suppress unwanted console output
+ sys.stdout = open(os.devnull, 'w')
+ sys.stderr = open(os.devnull, 'w')
+
+ driver = webdriver.Chrome(service=service, options=chromeOptions)
+
+ # Restore standard output and error streams
+ sys.stdout = sys.__stdout__
+ sys.stderr = sys.__stderr__
+
+ # Initialize the database and add items from config
+ initialize_db()
+ items = [(item['name'], item['link'], item['auto_buy'], item['quantity'], False) for item in config['available']['items']]
+ add_items(items)
+
+ test_mode = config['debug'].get('test_mode', False)
+ open_browser = config['app'].get('open_browser', False)
+
+ while True:
+ writeLog("Starting new iteration of item checks", "INFO")
+ for item in get_items():
+ name, link, auto_buy, quantity, purchased = item
+ if purchased:
+ writeLog(f"{name} has already been purchased", "INFO")
+ continue
+ if "amazon.com" in link:
+ writeLog(f"Checking availability for Amazon item: {name}", "INFO")
+ driver.get(link)
+ if detect_captcha(driver):
+ writeLog("CAPTCHA detected. Please solve it manually.", "WARNING")
+ play_notification_sound()
+ input("Press Enter after solving the CAPTCHA...")
+ available = check_amazon_item(driver, link)
+ if available:
+ play_available_sound()
+ short_url = make_tiny(link)
+ writeLog(f"{name} is available: {short_url}", "SUCCESS")
+ if auto_buy:
+ writeLog(f"Attempting to auto-buy {name} on Amazon", "INFO")
+ auto_buy_amazon_item(driver, link, config, quantity, test_mode)
+ else:
+ writeLog(f"{name} is available but auto-buy is disabled", "INFO")
+ if open_browser:
+ writeLog(f"Opening browser for {name}", "INFO")
+ webbrowser.open(link)
+ else:
+ writeLog(f"{name} is not available", "INFO")
+ elif "bestbuy.com" in link:
+ writeLog(f"Checking availability for BestBuy item: {name}", "INFO")
+ available = check_bestbuy_item(driver, link)
+ if available:
+ play_available_sound()
+ short_url = make_tiny(link)
+ writeLog(f"{name} is available: {short_url}", "SUCCESS")
+ if auto_buy:
+ writeLog(f"Attempting to auto-buy {name} on BestBuy", "INFO")
+ if not test_mode:
+ auto_buy_bestbuy_item(driver, link, config['app']['bb_email'], config['app']['bb_password'], config['app']['bb_cvv'], quantity)
+ play_buy_sound()
+ else:
+ writeLog(f"Test mode active: Skipping final purchase step", "INFO")
+ else:
+ writeLog(f"{name} is available but auto-buy is disabled", "INFO")
+ if open_browser:
+ writeLog(f"Opening browser for {name}", "INFO")
+ webbrowser.open(link)
+ else:
+ writeLog(f"{name} is not available", "INFO")
+ else:
+ writeLog(f"Unsupported URL: {link}", "WARNING")
+
+if __name__ == "__main__":
+ logger = setup_logger()
+ main()
\ No newline at end of file
diff --git a/models.py b/models.py
new file mode 100644
index 0000000..7e990ac
--- /dev/null
+++ b/models.py
@@ -0,0 +1,59 @@
+import sqlite3
+import os
+
+DB_PATH = os.path.join('data', 'shop_py_bot.db')
+
+def initialize_db(delete=False):
+ if delete and os.path.exists(DB_PATH):
+ os.remove(DB_PATH)
+ conn = sqlite3.connect(DB_PATH)
+ cursor = conn.cursor()
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS items (
+ id INTEGER PRIMARY KEY,
+ name TEXT NOT NULL,
+ link TEXT NOT NULL UNIQUE,
+ auto_buy BOOLEAN NOT NULL,
+ quantity INTEGER NOT NULL,
+ purchased BOOLEAN NOT NULL DEFAULT 0
+ )
+ ''')
+ conn.commit()
+ conn.close()
+
+def add_items(items):
+ conn = sqlite3.connect(DB_PATH)
+ cursor = conn.cursor()
+ for item in items:
+ cursor.execute('''
+ SELECT COUNT(*) FROM items WHERE link = ?
+ ''', (item[1],))
+ if cursor.fetchone()[0] == 0:
+ cursor.execute('''
+ INSERT INTO items (name, link, auto_buy, quantity, purchased)
+ VALUES (?, ?, ?, ?, ?)
+ ''', item)
+ conn.commit()
+ conn.close()
+
+def update_item_purchased(link):
+ conn = sqlite3.connect(DB_PATH)
+ cursor = conn.cursor()
+ cursor.execute('''
+ UPDATE items
+ SET purchased = 1
+ WHERE link = ?
+ ''', (link,))
+ conn.commit()
+ conn.close()
+
+def get_items():
+ conn = sqlite3.connect(DB_PATH)
+ cursor = conn.cursor()
+ cursor.execute('''
+ SELECT name, link, auto_buy, quantity, purchased
+ FROM items
+ ''')
+ items = cursor.fetchall()
+ conn.close()
+ return items
\ No newline at end of file
diff --git a/readme.md b/readme.md
index 3f922f6..2ceb1ff 100644
--- a/readme.md
+++ b/readme.md
@@ -1,4 +1,4 @@
-# ShopPyBot [](https://clan.bravebearstudios.com) [](paypal.me/BraveBearStudios)
+# ShopPyBot
*master*

@@ -10,7 +10,8 @@


-A Python based system to 1) attempt to purchase an item from a link; and 2) check the availability of a list of items. This project takes advantage of the systems provided through Selenium in order to interact with shop web pages. This (as of writing) does not integrate with any shop APIs.
+## Overview
+ShopPyBot is a bot designed to automate the process of checking availability and purchasing items from online stores like Amazon and BestBuy.
### Disclaimer
@@ -18,98 +19,79 @@ WARNING: The use of this software can result in a Amazon restricting access to y
Account restrictions may be triggered by any of the following: 1) running multiple instances on one device, 2) running multiple instances on different devices, using the same account, regardless of their IP, proxy, or location, 3) configuring an instance to check stock too frequently/aggressively (default settings not guaranteed to be safe).
-## Supported sites
+## Features
-- [x] Best Buy
-- [x] Amazon
-- [ ] Newegg
+- Automated availability checks
+- Automated purchasing
+- CAPTCHA detection and notification
+- Configurable via `config.yml`
+- Logging and error handling
-## Requirements
+## Setup
-- [Python](https://www.python.org/downloads/)
-- Selenium
- - `pip install selenium`
-- Playsound
- - `pip install playsound`
-- [Google Chrome](https://chrome.google.com)
-- [ChromeDriver](https://chromedriver.chromium.org/downloads)
- - Drop this in the same directory as `bot.py` and `bot-availCheck.py`
+### Prerequisites
-### Best Buy
+- Python 3.8+
+- pip (Python package installer)
+
+#### Best Buy
- A BestBuy account ([create one](https://www.bestbuy.com/identity/global/createAccount)) with a saved [payment method](https://www.bestbuy.com/profile/c/billinginfo/cc) (credit card)
-### Amazon
+#### Amazon
- A valid Amazon account (presave your [address](https://smile.amazon.com/a/addresses) and [payment method](https://smile.amazon.com/cpe/yourpayments/wallet)!)
- Your OTP device on hand (manual login required)
-## How to Use
-
-1. Make sure you have all the listed requirements above installed on your machine.
- - Windows users can execute the supplied `installDependencies.ps1` script to walk through the requirements setup process
-2. Customize `settings.json` to include all of your appropriate information. Use the tables below if you are unsure of what values you should use.
-3. Run `bot.py` or `bot-availCheck.py` through your favorite method
- - *NOTE:* It is recommended to run this through the command line to more easily observe any output that may come up
-
-### Link Processor
-
-To make the population of links for the availability check easier to process, modify `data.csv` such that each line is a pair of `item name, link to BestBuy item`. Once you have all your new items accounted for, run `processor.ps1` to generate new content in `out.json`. Copy and paste the contents of the `data` array inside of `out.json` to the `items` array in the `available` `settings.json` section.
+### Installation
-## Customization
+1. Clone the repository:
-Before putting the bot to work, you need to configure `settings.json` so that the scripts will function correctly. Be sure not to commit or otherwise save your sensitive information in a public place (email, password, cvv, etc.). Non-GPU items from BestBuy should work but it is not guranteed.
+```sh
+git clone https://github.com/yourusername/ShopPyBot.git
+cd ShopPyBot
+```
-OOtB the availability bot has a long list of RTX 30 series cards available on Best Buy; however, you will need to check the validity of this list to ensure your checks are up to date.
+2. Create and activate a virtual environment:
-### Debug
+```sh
+python -m venv .venv
+.venv\Scripts\activate # On Windows
+source .venv/bin/activate # On macOS/Linux
+```
-|Key|Description| Default |
-| --- | --- | --- |
-|loggingLevel|Set the level of logging in the bot script such that