├── output.png ├── .gitattributes ├── bot-vid-3.gif ├── requirements.txt ├── bots ├── __pycache__ │ ├── amazon.cpython-39.pyc │ ├── bestbuy.cpython-37.pyc │ ├── bestbuy.cpython-39.pyc │ ├── browser.cpython-39.pyc │ ├── fakeai.cpython-39.pyc │ ├── logger.cpython-39.pyc │ ├── newegg.cpython-37.pyc │ ├── newegg.cpython-39.pyc │ ├── printer.cpython-39.pyc │ ├── purchase.cpython-39.pyc │ ├── textbox.cpython-39.pyc │ ├── messenger.cpython-37.pyc │ └── messenger.cpython-39.pyc ├── messenger.py ├── logger.py ├── textbox.py ├── fakeai.py ├── printer.py ├── botutils │ └── paths.py ├── browser.py ├── amazon.py ├── bestbuy.py ├── NewEgg.py ├── newegg.py └── purchase.py ├── msg_tst_2.py ├── README.md ├── .gitignore ├── mysettings.py ├── app.py └── settings.json /output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysu1313/Quick-GPU-Bot/HEAD/output.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /bot-vid-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysu1313/Quick-GPU-Bot/HEAD/bot-vid-3.gif -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysu1313/Quick-GPU-Bot/HEAD/requirements.txt -------------------------------------------------------------------------------- /bots/__pycache__/amazon.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysu1313/Quick-GPU-Bot/HEAD/bots/__pycache__/amazon.cpython-39.pyc -------------------------------------------------------------------------------- /bots/__pycache__/bestbuy.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysu1313/Quick-GPU-Bot/HEAD/bots/__pycache__/bestbuy.cpython-37.pyc -------------------------------------------------------------------------------- /bots/__pycache__/bestbuy.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysu1313/Quick-GPU-Bot/HEAD/bots/__pycache__/bestbuy.cpython-39.pyc -------------------------------------------------------------------------------- /bots/__pycache__/browser.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysu1313/Quick-GPU-Bot/HEAD/bots/__pycache__/browser.cpython-39.pyc -------------------------------------------------------------------------------- /bots/__pycache__/fakeai.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysu1313/Quick-GPU-Bot/HEAD/bots/__pycache__/fakeai.cpython-39.pyc -------------------------------------------------------------------------------- /bots/__pycache__/logger.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysu1313/Quick-GPU-Bot/HEAD/bots/__pycache__/logger.cpython-39.pyc -------------------------------------------------------------------------------- /bots/__pycache__/newegg.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysu1313/Quick-GPU-Bot/HEAD/bots/__pycache__/newegg.cpython-37.pyc -------------------------------------------------------------------------------- /bots/__pycache__/newegg.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysu1313/Quick-GPU-Bot/HEAD/bots/__pycache__/newegg.cpython-39.pyc -------------------------------------------------------------------------------- /bots/__pycache__/printer.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysu1313/Quick-GPU-Bot/HEAD/bots/__pycache__/printer.cpython-39.pyc -------------------------------------------------------------------------------- /bots/__pycache__/purchase.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysu1313/Quick-GPU-Bot/HEAD/bots/__pycache__/purchase.cpython-39.pyc -------------------------------------------------------------------------------- /bots/__pycache__/textbox.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysu1313/Quick-GPU-Bot/HEAD/bots/__pycache__/textbox.cpython-39.pyc -------------------------------------------------------------------------------- /bots/__pycache__/messenger.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysu1313/Quick-GPU-Bot/HEAD/bots/__pycache__/messenger.cpython-37.pyc -------------------------------------------------------------------------------- /bots/__pycache__/messenger.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysu1313/Quick-GPU-Bot/HEAD/bots/__pycache__/messenger.cpython-39.pyc -------------------------------------------------------------------------------- /bots/messenger.py: -------------------------------------------------------------------------------- 1 | from twilio.rest import Client 2 | 3 | CLIENT = Client("your-UID-here", "your-API-key-here") 4 | 5 | def send_message(to_number, message): 6 | CLIENT.messages.create(to=to_number, 7 | from_="your-twilio-number-here", 8 | body="rtx bot reporting for duty: " + message) 9 | 10 | -------------------------------------------------------------------------------- /msg_tst_2.py: -------------------------------------------------------------------------------- 1 | from twilio.rest import Client 2 | import os 3 | 4 | 5 | # change the "from_" number to your Twilio number and the "to" number 6 | # to the phone number you signed up for Twilio with, or upgrade your 7 | # account to send SMS to any phone number 8 | Client.messages.create(to="13369291348", 9 | from_="8039747673", 10 | body="rtx bot reporting for duty") 11 | -------------------------------------------------------------------------------- /bots/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | from datetime import datetime 4 | 5 | class Logger(): 6 | def __init__(self, file_handler): 7 | self.file_handler = file_handler 8 | self.logger = logging.getLogger() 9 | self.logger.addHandler(self.file_handler) 10 | self.logger.setLevel(logging.INFO) 11 | self.logger = logging.getLogger() 12 | 13 | def log_info(self, msg): 14 | date = str(datetime.now().strftime("%d/%m/%Y %H:%M:%S")) + " ===> " 15 | self.logger.log(logging.INFO, date + msg) 16 | 17 | def log_warning(self, msg): 18 | date = str(datetime.now().strftime("%d/%m/%Y %H:%M:%S")) + " ===> " 19 | self.logger.log(logging.WARNING, date + msg) 20 | 21 | def log_error(self, msg): 22 | date = str(datetime.now().strftime("%d/%m/%Y %H:%M:%S")) + " ===> " 23 | self.logger.log(logging.ERROR, date + msg) 24 | -------------------------------------------------------------------------------- /bots/textbox.py: -------------------------------------------------------------------------------- 1 | import tkinter 2 | 3 | 4 | class TextEntry(): 5 | def __init__(self): 6 | 7 | self.top = tkinter.Tk() 8 | self.top.geometry("400x240") 9 | self.textBox = None 10 | self.label = tkinter.Label(self.top, text ="<==========> Enter Newegg 2FA code below: <==========>", takefocus=True) 11 | self.submitBtn = tkinter.Button(self.top, text="Submit", command=self.__setText) 12 | self.textBox = tkinter.Entry(self.top, text ="Hello", takefocus=True) 13 | self.result = "" 14 | 15 | def getTextInput(self): 16 | return self.result 17 | 18 | def __setText(self): 19 | self.result = self.textBox.get() 20 | self.top.destroy() 21 | 22 | def showField(self): 23 | #value = textBox.get() 24 | self.label.pack() 25 | self.textBox.pack() 26 | self.submitBtn.pack() 27 | self.top.mainloop() 28 | 29 | -------------------------------------------------------------------------------- /bots/fakeai.py: -------------------------------------------------------------------------------- 1 | 2 | ''' 3 | Portions of this code were used from: 4 | https://github.com/guilhermebferreira/selenium-notebooks/blob/master/Mouse%20move%20by%20b-spline%20interpolation.ipynb 5 | 6 | Thanks to guilhermebferreira 7 | ''' 8 | 9 | from selenium.webdriver.common.action_chains import ActionChains 10 | from selenium import webdriver 11 | import scipy.interpolate as si 12 | from time import sleep 13 | import numpy as np 14 | import time 15 | import os 16 | 17 | 18 | class FakeAI(): 19 | def __init__(self, driver): 20 | self.driver = driver 21 | self.x_i = 0 22 | self.y_i = 0 23 | self.__get_lin_curves() 24 | 25 | def __get_lin_curves(self): 26 | points = [[-6, 2], [-3, -2],[0, 0], [0, 2], [2, 3], [4, 0], [6, 3], [8, 5], [8, 8], [6, 8], [5, 9], [7, 2]]; 27 | points = np.array(points) 28 | x = points[:,0] 29 | y = points[:,1] 30 | t = range(len(points)) 31 | ipl_t = np.linspace(0.0, len(points) - 1, 100) 32 | x_tup = si.splrep(t, x, k=3) 33 | y_tup = si.splrep(t, y, k=3) 34 | x_list = list(x_tup) 35 | xl = x.tolist() 36 | x_list[1] = xl + [0.0, 0.0, 0.0, 0.0] 37 | y_list = list(y_tup) 38 | yl = y.tolist() 39 | y_list[1] = yl + [0.0, 0.0, 0.0, 0.0] 40 | self.x_i = si.splev(ipl_t, x_list) 41 | self.y_i = si.splev(ipl_t, y_list) 42 | 43 | def move_cursor(self, element): 44 | action = ActionChains(self.driver); 45 | #First, go to your start point or Element 46 | time.sleep(1) 47 | action.move_to_element(element); 48 | time.sleep(1) 49 | action.perform(); 50 | 51 | for mouse_x, mouse_y in zip(self.x_i, self.y_i): 52 | action.move_by_offset(mouse_x,mouse_y); 53 | action.perform(); 54 | sleep(0.1) 55 | print(mouse_x, mouse_y) 56 | 57 | 58 | #if __name__ == '__main__': 59 | # tmp = Human() 60 | # tmp.pretend_to_be_human() -------------------------------------------------------------------------------- /bots/printer.py: -------------------------------------------------------------------------------- 1 | 2 | from colorama import Fore, Style, init 3 | 4 | class Printer(): 5 | 6 | def __init__(self): 7 | self.old_prices = {} 8 | self.avg_prices = {} 9 | 10 | def output(self, ctype, success, price, details, link, stream_mode, platform): 11 | if success == "IN": 12 | self.pretty_print(Fore.GREEN, success, price, ctype, platform, details, link) 13 | # We found one in stock! 14 | #self.found_product(ctype, link) 15 | elif stream_mode: 16 | if success == "EXP": 17 | self.pretty_print(Fore.YELLOW, success, price, ctype, platform, details, link) 18 | elif success == "OUT": 19 | self.pretty_print(Fore.RED, success, price, ctype, platform, details, link) 20 | else: 21 | print("No data found") 22 | Style.RESET_ALL 23 | 24 | def print_message(self, color, message): 25 | print(color, message) 26 | 27 | def pretty_print(self, color, success, price, ctype, platform, detail, link): 28 | print(color + success, end=" ") 29 | if 'Ti' not in ctype: 30 | print(" | ${} | {} | {} | {} | {}".format( 31 | price, ctype, platform, detail.split("\n")[0], link)) 32 | else: 33 | print(" | ${} | {} | {} | {} | {}".format( 34 | price, ctype, platform, detail.split("\n")[0], link)) 35 | 36 | def print_refresh(self, count, dt_string, avg_prices, old_prices): 37 | self.old_prices = old_prices 38 | self.avg_prices = avg_prices 39 | print('\n<=======================BestBuy Refresh #:{}, {}=======================>\n'.format( 40 | count, dt_string), self.print_stats()) 41 | 42 | def print_stats(self): 43 | print(Fore.YELLOW, "Average Prices:") 44 | for key in self.old_prices: 45 | if self.old_prices[key] > self.avg_prices[key]: 46 | print(Fore.GREEN, "{} ${:.2f}".format(key, self.avg_prices[key]), end=" ") 47 | else: 48 | print(Fore.YELLOW, "{} ${:.2f}".format(key, self.avg_prices[key]), end=" ") -------------------------------------------------------------------------------- /bots/botutils/paths.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Paths(): 4 | def __init__(self): 5 | tmp = None 6 | 7 | # GENERAL # 8 | 9 | self.error_loop = "Error in loop_body: {}" 10 | 11 | 12 | # BEST BUY # 13 | 14 | self.BEST_BUY_PDP_URL = "https://api.bestbuy.com/click/5592e2b895800000/{sku}/pdp" 15 | self.BEST_BUY_CART_URL = "https://api.bestbuy.com/click/5592e2b895800000/{sku}/cart" 16 | self.BEST_BUY_PDP_URL = "https://api.bestbuy.com/click/5592e2b895800000/{sku}/pdp" 17 | self.BEST_BUY_CART_URL = "https://api.bestbuy.com/click/5592e2b895800000/{sku}/cart" 18 | self.BEST_BUY_ADD_TO_CART_API_URL = "https://www.bestbuy.com/cart/api/v1/addToCart" 19 | self.BEST_BUY_CHECKOUT_URL = "https://www.bestbuy.com/checkout/c/orders/{order_id}/" 20 | self.DEFAULT_HEADERS = { 21 | "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", 22 | "accept-encoding": "gzip, deflate, br", 23 | "accept-language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7", 24 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36", 25 | "origin": "https://www.bestbuy.com", 26 | } 27 | self.signin = "https://www.bestbuy.com/identity/global/signin" 28 | self.submit_login = "/html/body/div[1]/div/section/main/div[2]/div[1]/div/div/div/div/div/form/div[4]/button" 29 | self.pw_field = '//*[@id="fld-p1"]' 30 | self.select_country = '/html/body/div[2]/div/div/div/div[1]/div[2]/a[2]' 31 | self.close_deals_popup = "//*[@id=\"widgets-view-email-modal-mount\"]/div/div/div[1]/div/div/div/div/button" 32 | 33 | 34 | # NEWEGG # 35 | 36 | self.nav_title = 'nav-complex-title' 37 | self.ne_dropdown = "//*[@value='96']/option[text()='96']" 38 | self.signin_button = "//*[contains(text(), 'Sign in / Register')]" 39 | self.close_popup = "popup-close" 40 | self.sign_email = "labeled-input-signEmail" 41 | self.input_pw = "labeled-input-password" 42 | self.human = """//*[contains(text(), 'Are you a human?')]""" 43 | self.captcha = "/html/body/div[1]/div[2]/p[5]/a" 44 | self.two_factor = """//*[@id="app"]/div/div[2]/div[2]/div/div/div[3]/form/div/div[3]/div/input[1]""" 45 | self.tfa_value = """//*[@id="app"]/div/div[2]/div[2]/div/div/div[3]/form/div/div[3]/div/input[6]""" 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Quick GPU Bot 2 | Definitely not my custom made web scraper bot for buying an RTX 3080. Nope definitely not that. 3 | 4 | [![MIT License](https://img.shields.io/apm/l/atomic-design-ui.svg?)](https://github.com/kysu1313/Quick-GPU-Bot/LICENSEs)[![Github All Releases](https://img.shields.io/github/downloads/kysu1313/Quick-GPU-Bot/total.svg?style=flat)]() [![Issues](https://img.shields.io/github/issues-raw/kysu1313/Quick-GPU-Bot)](https://github.com/kysu1313/Quick-GPU-Bot/issues) [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome) 5 | 6 | 7 | __QUICK SETUP__: 8 | 1. If using Chrome (_Recommended_): 9 | Download ChromeDriver from (https://chromedriver.chromium.org/downloads) and add the .exe path to the "custom_chrome_exe_path" in settings.json 10 | 2. If using FireFox: 11 | Download GeckoDriver from here: (https://github.com/mozilla/geckodriver/releases) and add it to your PATH environment variables. 12 | 3. Add your preferences, shipping, payment and site log-in information to the 'settings.json' file. 13 | *Note: The important setting fields are denoted by a smily face emoji. 14 | 5. Install requirements from requirements.txt with ```pip install -r requirements.txt``` 15 | 6. If you want to use this bot and have it text you when it gets a hit. You will need to make a developer account on Twilio (https://www.twilio.com/docs/iam/keys/api-key) and add your private keys to the mssage.py class. 16 | Like this: 17 | ```CLIENT = Client("Account SID", "Auth Token")``` 18 | You can disable messaging in the settings file if you want. 19 | 6. To Run: ```py app.py``` in the command line 20 | 21 | "Demo with console output:" 22 | ![Stock checking](bot-vid-3.gif) 23 | 24 | SUPPORT: 25 | - Currently supports Firefox and Chrome browser drivers 26 | - ** Only supports BestBuy and Newegg ** However B&H Photo and Amazon are in the works. 27 | 28 | NOTES: 29 | - If you run the bot using headless mode, when the 2-factor auth is required from newegg on startup, a small tkinter textbox will popup. Just submit your 2FA code there and the program will continue. 30 | - 'DEBUG_MODE' must be set to 'True' for any of the debug settings to take effect. 31 | - You can add custom url's to the 'URLs' section in the settings.json file 32 | - If you are getting "selenium.common.exceptions.WebDriverException: Message: '' executable needs to be in PATH." You just need to add the chrome driver path to the settings.json file field named "custom_chrome_exe_path" 33 | 34 | 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | newtest/* 30 | .profile-bb 31 | __pycache__ 32 | bots/__pycache__ 33 | bots/__pycache__/* 34 | *.pyc 35 | 36 | # PyInstaller 37 | # Usually these files are written by a python script from a template 38 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 39 | *.manifest 40 | *.spec 41 | 42 | # Installer logs 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .nox/ 50 | .coverage 51 | .coverage.* 52 | .cache 53 | nosetests.xml 54 | coverage.xml 55 | *.cover 56 | *.py,cover 57 | .hypothesis/ 58 | .pytest_cache/ 59 | cover/ 60 | 61 | # Translations 62 | *.mo 63 | *.pot 64 | 65 | # Django stuff: 66 | *.log 67 | local_settings.py 68 | db.sqlite3 69 | db.sqlite3-journal 70 | 71 | # Flask stuff: 72 | instance/ 73 | .webassets-cache 74 | 75 | # Scrapy stuff: 76 | .scrapy 77 | 78 | # Sphinx documentation 79 | docs/_build/ 80 | 81 | # PyBuilder 82 | .pybuilder/ 83 | target/ 84 | 85 | # Jupyter Notebook 86 | .ipynb_checkpoints 87 | 88 | # IPython 89 | profile_default/ 90 | ipython_config.py 91 | 92 | # pyenv 93 | # For a library or package, you might want to ignore these files since the code is 94 | # intended to run in multiple environments; otherwise, check them in: 95 | # .python-version 96 | 97 | # pipenv 98 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 99 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 100 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 101 | # install all needed dependencies. 102 | #Pipfile.lock 103 | 104 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 105 | __pypackages__/ 106 | 107 | # Celery stuff 108 | celerybeat-schedule 109 | celerybeat.pid 110 | 111 | # SageMath parsed files 112 | *.sage.py 113 | 114 | # Environments 115 | .env 116 | .venv 117 | env/ 118 | venv/ 119 | ENV/ 120 | env.bak/ 121 | venv.bak/ 122 | 123 | # Spyder project settings 124 | .spyderproject 125 | .spyproject 126 | 127 | # Rope project settings 128 | .ropeproject 129 | 130 | # mkdocs documentation 131 | /site 132 | 133 | # mypy 134 | .mypy_cache/ 135 | .dmypy.json 136 | dmypy.json 137 | 138 | # Pyre type checker 139 | .pyre/ 140 | 141 | # pytype static type analyzer 142 | .pytype/ 143 | 144 | # Cython debug symbols 145 | cython_debug/ 146 | *.pyc 147 | -------------------------------------------------------------------------------- /bots/browser.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.common.keys import Keys 3 | from selenium.webdriver.common.by import By 4 | from selenium.common.exceptions import NoSuchElementException 5 | from selenium.webdriver.support.ui import WebDriverWait 6 | from selenium.webdriver.chrome.options import Options 7 | from selenium.webdriver.support import expected_conditions as EC 8 | from webdriver_manager.chrome import ChromeDriverManager 9 | 10 | class Browser(): 11 | def __init__(self, settings): 12 | # Custom settings 13 | self.load_images = settings.load_images 14 | self.headless_mode = settings.headless_mode 15 | self.save_logs = settings.save_logs 16 | self.use_custom_urls = settings.use_custom_urls 17 | self.custom_urls = settings.custom_urls 18 | self.settings = settings 19 | 20 | self.driver = self.get_driver() 21 | 22 | def str2bool(self, str): 23 | return str.lower() == 'true' 24 | 25 | def driver_wait(self, id_type, text, max_wait=4): 26 | WebDriverWait(self.driver, max_wait).until( 27 | EC.presence_of_element_located((id_type, text)) 28 | ) 29 | 30 | def get_driver(self): 31 | if self.use_custom_urls: 32 | self.url = self.custom_urls 33 | if self.settings.use_chrome: 34 | options = Options() 35 | #options.binary_location(executable_path=self.settings.custom_chrome_exe_path) 36 | options.add_argument('--disable-dev-shm-usage') 37 | #options.binary_location = self.settings.custom_chrome_exe_path 38 | options.page_load_strategy = "eager" 39 | options.add_experimental_option("excludeSwitches", ["enable-automation"]) 40 | options.add_experimental_option("useAutomationExtension", False) 41 | #options.add_argument('--no-proxy-server') 42 | #options.add_argument("--proxy-server='direct://'") 43 | #options.add_argument("--proxy-bypass-list=*") 44 | #options.add_argument('--blink-settings=imagesEnabled=false') 45 | #options.add_argument("--no-sandbox"); 46 | if not self.load_images: 47 | prefs = {"profile.managed_default_content_settings.images": 2} 48 | options.add_experimental_option("prefs", prefs) 49 | if self.headless_mode: 50 | options.add_argument('--headless') 51 | options.add_argument('--no-proxy-server') 52 | if self.settings.use_chrome_profile: 53 | options.add_argument("--user-data-dir={}".format(self.settings.chrome_profile_path)) # .profile-bb 54 | options.add_argument('--profile-directory=Default') 55 | if self.settings.custom_chrome_exe_path != "" or None: 56 | #driver = webdriver.Chrome(self.settings.custom_chrome_exe_path, options=options) 57 | driver = webdriver.Chrome(ChromeDriverManager().install(), options=options) 58 | else: 59 | driver = webdriver.Chrome(options=options) 60 | else: 61 | fireFoxOptions = webdriver.FirefoxOptions() 62 | firefox_profile = webdriver.FirefoxProfile(self.settings.custom_firefox_exe_path) 63 | if self.headless_mode: 64 | fireFoxOptions.set_headless() 65 | elif not self.load_images: 66 | firefox_profile.set_preference('permissions.default.image', 2) 67 | firefox_profile.set_preference('dom.ipc.plugins.enabled.libflashplayer.so', 'false') 68 | driver = webdriver.Firefox(firefox_profile=firefox_profile, firefox_options=fireFoxOptions) 69 | return driver -------------------------------------------------------------------------------- /mysettings.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | import os 4 | 5 | 6 | class Settings: 7 | 8 | def __init__( 9 | self, 10 | DEBUG_MODE, 11 | debug, 12 | run_bestbuy, 13 | run_newegg, 14 | newegg_urls, 15 | SELECTED_NEWEGG_URL, 16 | bestbuy_urls, 17 | SELECTED_BESTBUY_URL, 18 | use_firefox, 19 | use_firefox_profile, 20 | custom_firefox_exe_path, 21 | use_chrome, 22 | use_chrome_profile, 23 | custom_chrome_exe_path, 24 | chrome_profile_path, 25 | stream_mode, 26 | instock_only_mode, 27 | send_messages, 28 | show_progress_bar, 29 | show_price_line, 30 | show_refresh_time, 31 | purchase_multiple_items, 32 | load_images, 33 | headless_mode, 34 | max_timeout, 35 | save_logs, 36 | use_custom_urls, 37 | custom_urls, 38 | cards_to_find, 39 | card_prices, 40 | 41 | login_at_start, 42 | checkout_as_guest, 43 | 44 | bb_info, 45 | ne_info, 46 | az_info, 47 | shipping_info, 48 | payment_info 49 | ): 50 | 51 | # Settings 52 | self.DEBUG_MODE = self.str2bool(DEBUG_MODE) 53 | self.debug = self.clean_card_dict(debug) 54 | self.run_bestbuy = self.str2bool(run_bestbuy) 55 | self.run_newegg = self.str2bool(run_newegg) 56 | self.newegg_urls = newegg_urls 57 | self.SELECTED_NEWEGG_URL = SELECTED_NEWEGG_URL 58 | self.bestbuy_urls = bestbuy_urls 59 | self.SELECTED_BESTBUY_URL = SELECTED_BESTBUY_URL 60 | self.use_firefox = self.str2bool(use_firefox) 61 | self.use_firefox_profile = self.str2bool(use_firefox_profile) 62 | self.custom_firefox_exe_path = custom_firefox_exe_path 63 | self.use_chrome = self.str2bool(use_chrome) 64 | self.use_chrome_profile = self.str2bool(use_chrome_profile) 65 | self.custom_chrome_exe_path = custom_chrome_exe_path 66 | self.chrome_profile_path = chrome_profile_path 67 | self.stream_mode = self.str2bool(stream_mode) 68 | self.instock_only_mode = self.str2bool(instock_only_mode) 69 | self.send_messages = self.str2bool(send_messages) 70 | self.show_progress_bar = self.str2bool(show_progress_bar) 71 | self.show_price_line = self.str2bool(show_price_line) 72 | self.show_refresh_time = self.str2bool(show_refresh_time) 73 | self.purchase_multiple_items = self.str2bool(purchase_multiple_items) 74 | self.load_images = self.str2bool(load_images) 75 | self.headless_mode = self.str2bool(headless_mode) 76 | self.max_timeout = max_timeout 77 | self.save_logs = self.str2bool(save_logs) 78 | self.use_custom_urls = self.str2bool(use_custom_urls) 79 | self.custom_urls = custom_urls 80 | self.cards_to_find = self.clean_card_dict(cards_to_find) 81 | self.card_prices = card_prices 82 | self.login_at_start = self.str2bool(login_at_start) 83 | self.checkout_as_guest = self.str2bool(checkout_as_guest) 84 | self.bb_info = bb_info 85 | self.ne_info = ne_info 86 | self.az_info = az_info 87 | self.shipping_info = shipping_info 88 | self.payment_info = payment_info 89 | 90 | self.browser = self.getBrowser() 91 | self.clean_info_dict() 92 | 93 | def str2bool(self, st): 94 | return st == 'True' 95 | 96 | def clean_card_dict(self, dct): 97 | for k in dct: 98 | v = dct[k] 99 | dct[k] = v == 'True' 100 | return dct 101 | 102 | def clean_info_dict(self): 103 | for k in self.bb_info: 104 | if "auto" in k: 105 | v = self.bb_info[k] 106 | self.bb_info[k] = v == 'True' 107 | for k in self.ne_info: 108 | if "auto" in k: 109 | v = self.ne_info[k] 110 | self.ne_info[k] = v == 'True' 111 | for k in self.az_info: 112 | if "auto" in k: 113 | v = self.az_info[k] 114 | self.az_info[k] = v == 'True' 115 | 116 | # Uses Firefox as default 117 | def getBrowser(self): 118 | if self.use_chrome == 'True' or self.use_chrome == True: 119 | self.browser = 'chrome' 120 | else: 121 | self.browser = 'firefox' 122 | 123 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.common.keys import Keys 2 | from bots import newegg, bestbuy, amazon, messenger 3 | from colorama import Fore, Style, init 4 | from jsoncomment import JsonComment 5 | from mysettings import Settings 6 | from selenium import webdriver 7 | import multiprocessing 8 | import threading 9 | import logging 10 | import random 11 | import time 12 | import json 13 | import re 14 | 15 | import os 16 | 17 | 18 | #NEWEGG_URL = "https://www.newegg.com/p/pl?d=rtx+30+series&PageSize=96" 19 | #NEWEGG_TEST = "https://www.newegg.com/p/pl?d=gpu&N=50001441" 20 | #NEWEGG_TEST_2 = "https://www.newegg.com/p/pl?d=gpu&N=100007709&isdeptsrh=1" 21 | 22 | #BESTBUY_ALL_URL = "https://www.bestbuy.com/site/searchpage.jsp?_dyncharset=UTF-8&id=pcat17071&iht=y&keys=keys&ks=960&list=n&qp=gpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203060%20Ti%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203070%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203080%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203090&sc=Global&st=rtx%20&type=page&usc=All%20Categories" 23 | #BESTBUY_3060_URL = "https://www.bestbuy.com/site/searchpage.jsp?_dyncharset=UTF-8&id=pcat17071&iht=y&keys=keys&ks=960&list=n&qp=gpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203060%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203060%20Ti&sc=Global&st=rtx%20&type=page&usc=All%20Categories" 24 | #BESTBUY_3080_URL = "https://www.bestbuy.com/site/searchpage.jsp?_dyncharset=UTF-8&id=pcat17071&iht=y&keys=keys&ks=960&list=n&qp=gpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203080&sc=Global&st=rtx%20&type=page&usc=All%20Categories" 25 | #BESTBUY_3090_URL = "https://www.bestbuy.com/site/searchpage.jsp?_dyncharset=UTF-8&id=pcat17071&iht=y&keys=keys&ks=960&list=n&qp=gpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203090&sc=Global&st=rtx%20&type=page&usc=All%20Categories" 26 | 27 | AMAZON_ALL_URL = "https://www.amazon.com/s?k=rtx+30+series+graphics+card&rh=n%3A284822&ref=nb_sb_noss" 28 | 29 | #BB_IN_STOCK_TEST = """https://www.bestbuy.com/site/searchpage.jsp?_dyncharset=UTF-8&id=pcat17071&iht=y&keys=keys&ks=960&list=n&qp=gpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20GT%201030%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20GT%20710%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20GTX%201060%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20GTX%201650%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203060%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203060%20Ti%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203070%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203070%20Ti%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203080%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203080%20Ti%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203090&sc=Global&st=gpu&type=page&usc=All%20Categories""" 30 | #BB_TEST = "https://www.bestbuy.com/site/searchpage.jsp?st=gpu&_dyncharset=UTF-8&_dynSessConf=&id=pcat17071&type=page&sc=Global&cp=1&nrp=&sp=&qp=&list=n&af=true&iht=y&usc=All+Categories&ks=960&keys=keys" 31 | #BB_AMD_TEST = "https://www.bestbuy.com/site/computer-cards-components/video-graphics-cards/abcat0507002.c?id=abcat0507002&qp=chipsetmanufacture_facet%3DChipset%20Manufacture~AMD" 32 | #BB_instock_test2 = "https://www.bestbuy.com/site/computer-cards-components/video-graphics-cards/abcat0507002.c?id=abcat0507002" 33 | 34 | threads = list() 35 | THREAD_X = None 36 | THREAD_Y = None 37 | 38 | 39 | 40 | def newegg_run(number, url, settings, debug=False): 41 | egg = newegg.NewEgg(number, url, settings, debug) 42 | egg.start() 43 | 44 | 45 | def bestbuy_run(number, url, settings, debug=False): 46 | best = bestbuy.BestBuy(number, url, settings, debug) 47 | best.start() 48 | 49 | 50 | def amazon_run(number, url, settings): 51 | azn = amazon.Amazon(number, url, settings) 52 | azn.start() 53 | 54 | def newegg_process_test(number, url, settings, debug=False): 55 | PROCESS_X = multiprocessing.Process( 56 | target=newegg_run, args=(number, settings.newegg_urls[settings.SELECTED_NEWEGG_URL], settings, debug)) 57 | PROCESS_X.start() 58 | PROCESS_X.join() 59 | 60 | def run_test(number, url, settings): 61 | 62 | if 'best' in url: 63 | print("Loading https://www.bestbuy.com.") 64 | elif 'new' in url: 65 | print("Loading https://www.newegg.com.") 66 | #egg = newegg.NewEgg(number, url, settings) 67 | azon = amazon.Amazon(number, url, settings) 68 | #print("Loading https://www.newegg.com.") 69 | #best = bestbuy.BestBuy(number, url, settings) 70 | #egg.start() 71 | #best.start() 72 | azon.start() 73 | 74 | def run_all(one, number, settings, debug=False): 75 | logging.info("Thread {}: starting".format("Newegg")) 76 | time.sleep(2) 77 | logging.info("Thread {}: starting".format("BestBuy")) 78 | 79 | PROCESS_X = multiprocessing.Process( 80 | target=newegg_run, args=(number, settings.newegg_urls[settings.SELECTED_NEWEGG_URL], settings, debug)) 81 | PROCESS_Y = multiprocessing.Process( 82 | target=bestbuy_run, args=(number, settings.bestbuy_urls[settings.SELECTED_BESTBUY_URL], settings, debug)) 83 | 84 | PROCESS_X.start() 85 | PROCESS_Y.start() 86 | PROCESS_X.join() 87 | PROCESS_Y.join() 88 | 89 | def parse_settings(): 90 | dir = os.getcwd() + '\\settings.json' 91 | f = open(dir, "r") 92 | json = JsonComment() 93 | data = json.loads(f.read()) 94 | sett = Settings(**data.get('settings')) 95 | return sett 96 | 97 | if __name__ == "__main__": 98 | # Your number here 99 | number = "9999999999" 100 | try: 101 | 102 | print("\n<=============== INITIALIZING ===============>") 103 | 104 | settings = parse_settings() 105 | if (settings.run_bestbuy and settings.run_newegg): 106 | run_all(number, settings) 107 | elif settings.run_newegg: 108 | newegg_run(number, settings.newegg_urls[settings.SELECTED_NEWEGG_URL], settings, True) 109 | elif settings.run_bestbuy: 110 | bestbuy_run(number, settings.bestbuy_urls[settings.SELECTED_BESTBUY_URL], settings) 111 | 112 | #bestbuy_run(number, settings.bestbuy_url[settings.SELECTED_BESTBUY_URL], settings) 113 | #newegg_run(number, settings.newegg_urls[settings.SELECTED_NEWEGG_URL], settings, True) 114 | #newegg_process_test(number, settings.newegg_urls[settings.SELECTED_NEWEGG_URL], settings, True) 115 | #amazon_run(number, AMAZON_ALL_URL, settings, True) 116 | run_all(1, number, settings, True) 117 | 118 | except KeyboardInterrupt: 119 | print("Terminating ...") 120 | THREAD_X.terminate() 121 | THREAD_Y.terminate() 122 | pass 123 | -------------------------------------------------------------------------------- /settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // NOTE: Fields are case sensitive 3 | 4 | // NOTE: Important fields are marked with: '😀' 5 | 6 | "settings": { 7 | // ========== DEBUG MODE ==============// 8 | // 😀 Use this is you need to tweak anything to prevent accidental purchases, 9 | // Learn from my mistakes... 10 | "DEBUG_MODE": "False", 11 | "debug": { 12 | "login_enabled": "True", 13 | 14 | // Set to 'False' if you want to test entering payment info 15 | // but don't want to make any purchases 16 | "purchase_enabled": "False", 17 | 18 | // Set to 'False' if you want to test the bot 19 | // without adding items to cart 20 | "test_payment_no_purchase": "True", 21 | "send_messages": "True" 22 | }, 23 | 24 | // ========== GENERAL SETTINGS ==============// 25 | // You can run newegg and bestbuy at the same time. 26 | "run_bestbuy": "True", 27 | "run_newegg": "True", 28 | 29 | // ========== URL's ============= // 30 | 31 | // URL's for sites. You can add a custom url, just make sure to update the SELECTED name. 32 | "newegg_urls": { 33 | "NEWEGG_URL": "https://www.newegg.com/p/pl?d=rtx+30+series&PageSize=96", 34 | "NEWEGG_TEST": "https://www.newegg.com/p/pl?d=gpu&N=50001441", 35 | "NEWEGG_TEST_2": "https://www.newegg.com/p/pl?d=gpu&N=100007709&isdeptsrh=1" 36 | }, 37 | // 😀 Name of Newegg url you want to use: 38 | "SELECTED_NEWEGG_URL": "NEWEGG_URL", 39 | 40 | "bestbuy_urls": { 41 | "BESTBUY_ALL_URL": "https://www.bestbuy.com/site/searchpage.jsp?_dyncharset=UTF-8&id=pcat17071&iht=y&keys=keys&ks=960&list=n&qp=gpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203060%20Ti%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203070%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203080%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203090&sc=Global&st=rtx%20&type=page&usc=All%20Categories", 42 | "BESTBUY_3060_URL": "https://www.bestbuy.com/site/searchpage.jsp?_dyncharset=UTF-8&id=pcat17071&iht=y&keys=keys&ks=960&list=n&qp=gpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203060%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203060%20Ti&sc=Global&st=rtx%20&type=page&usc=All%20Categories", 43 | "BESTBUY_3080_URL": "https://www.bestbuy.com/site/searchpage.jsp?_dyncharset=UTF-8&id=pcat17071&iht=y&keys=keys&ks=960&list=n&qp=gpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203080&sc=Global&st=rtx%20&type=page&usc=All%20Categories", 44 | "BESTBUY_3090_URL": "https://www.bestbuy.com/site/searchpage.jsp?_dyncharset=UTF-8&id=pcat17071&iht=y&keys=keys&ks=960&list=n&qp=gpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203090&sc=Global&st=rtx%20&type=page&usc=All%20Categories", 45 | "BB_IN_STOCK_TEST": "https://www.bestbuy.com/site/searchpage.jsp?_dyncharset=UTF-8&id=pcat17071&iht=y&keys=keys&ks=960&list=n&qp=gpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20GT%201030%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20GT%20710%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20GTX%201060%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20GTX%201650%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203060%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203060%20Ti%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203070%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203070%20Ti%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203080%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203080%20Ti%5Egpusv_facet%3DGraphics%20Processing%20Unit%20(GPU)~NVIDIA%20GeForce%20RTX%203090&sc=Global&st=gpu&type=page&usc=All%20Categories", 46 | "BB_TEST": "https://www.bestbuy.com/site/searchpage.jsp?st=gpu&_dyncharset=UTF-8&_dynSessConf=&id=pcat17071&type=page&sc=Global&cp=1&nrp=&sp=&qp=&list=n&af=true&iht=y&usc=All+Categories&ks=960&keys=keys", 47 | "BB_AMD_TEST": "https://www.bestbuy.com/site/computer-cards-components/video-graphics-cards/abcat0507002.c?id=abcat0507002&qp=chipsetmanufacture_facet%3DChipset%20Manufacture~AMD", 48 | "BB_instock_test2": "https://www.bestbuy.com/site/computer-cards-components/video-graphics-cards/abcat0507002.c?id=abcat0507002" 49 | }, 50 | // 😀 Name of Newegg url you want to use: 51 | "SELECTED_BESTBUY_URL": "BESTBUY_ALL_URL", 52 | 53 | // ========== BROWSER CONTROL ============= // 54 | 55 | // Browser selection and path (if requied) 56 | "use_firefox": "False", 57 | "use_firefox_profile": "False", 58 | // 😀 Replace '' with your username 59 | "custom_firefox_exe_path": "C:\\Users\\\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\al32vdl0.default-release", 60 | // If you want to use your browser profile, add the folder path here, otherwise leave it empty 61 | "use_chrome": "True", 62 | "use_chrome_profile": "False", 63 | // Replace '' with your username 64 | "chrome_profile_path": "C:\\Users\\\\AppData\\Local\\Google\\Chrome\\User Data\\", 65 | // 😀 Add driver path 66 | "custom_chrome_exe_path": "D:\\ChromeDriver\\chromedriver.exe", 67 | // Loading images is slow 68 | "load_images": "False", 69 | 70 | // Loading a webpage is also slow. You can disable it here. 71 | // When using headless mode, if you have to enter a 2FA code for Newegg, a small popupbox will appear. 72 | // You type/paste the code in and press submit. 73 | "headless_mode": "False", 74 | 75 | // WARNING: Logging is a little messed up. Occasionally it will produce massive files. (Fixes are on the way) 76 | "save_logs": "True", 77 | "use_custom_urls": "False", 78 | "custom_urls": "None", 79 | // Maximum time to wait for html element to appear 80 | "max_timeout": 10, 81 | 82 | // ============= VISUAL SETTINGS ============= // 83 | 84 | // Show console output for every item checked, or only when in stock item is found 85 | "stream_mode": "True", 86 | "instock_only_mode": "False", 87 | // Progress bar only used with 'instock_only_mode' = 'True' 88 | "show_progress_bar": "False", 89 | "show_price_line": "False", 90 | "show_refresh_time": "True", 91 | 92 | // ============ MESSAGE SETTINGS ============ // 93 | 94 | // *** Send Text Message On Hit *** 95 | "send_messages": "False", 96 | 97 | // =========== PRODUCT SETTINGS =========== // 98 | 99 | // Purchase one item or many GPU's 100 | // If 'True' script keeps running after purchase 101 | "purchase_multiple_items": "False", 102 | 103 | // The keys in 'cards_to_find' and 'card_prices' must be the same. 104 | // So if you add something to the list to find, you must also add it to the price list. 105 | // 😀 Item(s) to search for 106 | "cards_to_find": { 107 | "3060 Ti": "True", 108 | "3080 Ti": "True", 109 | "3060": "True", 110 | "3070": "True", 111 | "3080": "True", 112 | "3090": "True" 113 | //"710": "True", 114 | //"RX 560": "True" 115 | }, 116 | 117 | // 😀 Highest price you want to pay for items 118 | "card_prices": { 119 | "3060 Ti": 750, 120 | "3080 Ti": 1600, 121 | "3060": 600, 122 | "3070": 800, 123 | "3080": 1200, 124 | "3090": 2500 125 | //"710": 200, 126 | //"RX 560": 500 127 | }, 128 | 129 | // =========== ACCOUNT SETTINGS =========== // 130 | 131 | // Login on start (Faster) 132 | // Note: Login at start is required for Newegg 133 | "login_at_start": "False", 134 | 135 | // Checking out as a guest is slower. 136 | // It's recommended to have your info pre-filled in your account and login at checkout 137 | "checkout_as_guest": "False", 138 | 139 | // 😀 BestBuy login / buy info 140 | "bb_info": { 141 | "bb_auto_login": "True", 142 | "bb_auto_buy": "False", 143 | "bb_username": "yourInfoHere", 144 | "bb_email": "yourInfoHere", 145 | "bb_password": "yourInfoHere" 146 | }, 147 | 148 | // 😀 Newegg login / buy info 149 | "ne_info": { 150 | "ne_auto_login": "True", 151 | "ne_auto_buy": "False", 152 | "ne_username": "yourInfoHere", 153 | "ne_email": "yourInfoHere", 154 | "ne_password": "yourInfoHere" 155 | }, 156 | 157 | // Amazon login / buy info 158 | "az_info": { 159 | "az_auto_login": "False", 160 | "az_auto_buy": "False", 161 | "az_username": "yourInfoHere", 162 | "az_email": "yourInfoHere", 163 | "az_password": "yourInfoHere" 164 | }, 165 | 166 | // 😀 Shipping info for use on all sites 167 | // Required for 'auto_buy' 168 | "shipping_info": { 169 | "first_name": "", 170 | "last_name": "", 171 | "address": "", 172 | "apartment": "", 173 | "zip_code": "", 174 | "city": "", 175 | "state": "", 176 | "phone_number": "" 177 | }, 178 | 179 | // 😀 Credit Card info 180 | // Only needed if auto-buy is enabled 181 | "payment_info": { 182 | "card_number": "555555555", 183 | "expiration_month": "02", 184 | "expiration_year": "2022", 185 | "security_code": 555 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /bots/amazon.py: -------------------------------------------------------------------------------- 1 | from selenium.common.exceptions import NoSuchElementException 2 | from selenium.webdriver.chrome.options import Options 3 | from selenium.webdriver.support import expected_conditions as EC 4 | from selenium.webdriver.common.keys import Keys 5 | from selenium.webdriver.common.by import By 6 | from colorama import Fore, Style, init 7 | from selenium import webdriver 8 | from datetime import datetime 9 | from bots import messenger as msg 10 | import logging 11 | from requests.packages.urllib3.util.retry import Retry 12 | from selenium.common.exceptions import TimeoutException 13 | from requests.adapters import HTTPAdapter 14 | import requests 15 | #from dotenv import load_dotenv 16 | from time import sleep 17 | from tqdm import tqdm 18 | from bots.browser import Browser 19 | from bots.printer import Printer 20 | from bots.purchase import Purchase 21 | import requests 22 | import random 23 | import time 24 | import re 25 | import os 26 | import re 27 | from selenium.webdriver.support.wait import WebDriverWait 28 | 29 | 30 | DEFAULT_HEADERS = { 31 | "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", 32 | "accept-encoding": "gzip, deflate, br", 33 | "accept-language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7", 34 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36", 35 | "origin": "https://www.newegg.com", 36 | } 37 | 38 | AMAZON_URLS = { 39 | "BASE_URL": "https://{domain}/", 40 | "ALT_OFFER_URL": "https://{domain}/gp/offer-listing/", 41 | "OFFER_URL": "https://{domain}/dp/", 42 | "CART_URL": "https://{domain}/gp/cart/view.html", 43 | "ATC_URL": "https://{domain}/gp/aws/cart/add.html", 44 | } 45 | 46 | MAX_TIMEOUT = 10 47 | 48 | class Amazon(): 49 | 50 | def __init__(self, number, url, settings): 51 | 52 | #load_dotenv() 53 | init(convert=True) 54 | self.logger = logging.getLogger() 55 | fhandler = logging.FileHandler(filename='.\\amazonLog.log', mode='a') 56 | self.logger.addHandler(fhandler) 57 | self.logger.setLevel(logging.INFO) 58 | #self.session = requests.Session() 59 | self.is_logged_in = False 60 | self.printer = Printer() 61 | self.az_base = "smile.amazon.com" 62 | if settings is not None: 63 | self.MAX_TIMEOUT = self.settings.max_timeout 64 | else: 65 | self.MAX_TIMEOUT = MAX_TIMEOUT 66 | 67 | # General settings 68 | self.settings = settings 69 | self.url = url 70 | self.stream_mode = settings.stream_mode 71 | self.instock_only_mode = settings.instock_only_mode 72 | 73 | # //*[@id="search"]/div[1]/div/div[1]/div/span[3]/div[2]/div[1]/div/span/div 74 | 75 | # What we're looking for 76 | self.cards_to_find = settings.cards_to_find 77 | self.card_prices = settings.card_prices 78 | 79 | # Login time 80 | self.login_at_start = settings.login_at_start 81 | 82 | # NewEgg settings 83 | self.az_info = settings.az_info 84 | 85 | self.number = number 86 | self.sku_id = sku_id = None 87 | 88 | # Browser driver 89 | self.browser = Browser(settings) 90 | self.driver = self.browser.driver 91 | self.driver.get(url) 92 | 93 | # Statistics 94 | self.avg_prices = self.total_prices = self.card_count = self.old_prices = { 95 | "3060" : 0, 96 | "3060 Ti" : 0, 97 | "3070" : 0, 98 | "3080" : 0, 99 | "3080 Ti" : 0, 100 | "3090" : 0 101 | } 102 | 103 | 104 | 105 | #adapter = HTTPAdapter( 106 | # max_retries=Retry( 107 | # total=3, 108 | # backoff_factor=1, 109 | # status_forcelist=[429, 500, 502, 503, 504], 110 | # method_whitelist=["HEAD", "GET", "OPTIONS", "POST"], 111 | # ) 112 | #) 113 | #self.session.mount("https://", adapter) 114 | #self.session.mount("http://", adapter) 115 | #self.session.get(self.url) 116 | 117 | #response = self.session.get( 118 | #NEWEGG_PDP_URL.format(sku=self.sku_id), headers=DEFAULT_HEADERS 119 | #) 120 | 121 | def close_popup(self, path): 122 | """ Close popup window """ 123 | 124 | self.driver.find_element_by_xpath(path).click() 125 | 126 | def get_chunks(self, desc): 127 | """ Break down description to extract only the first part. """ 128 | 129 | chunks, chunk_size = len(desc), len(desc)//4 130 | pts = [desc[i:i+chunk_size] for i in range(0, chunks, chunk_size)] 131 | return pts[0] 132 | 133 | def get_timeout(self, timeout=MAX_TIMEOUT): 134 | return time.time() + timeout 135 | 136 | def login(self): 137 | timeout = self.get_timeout() 138 | while True: 139 | try: 140 | email_field = self.driver.find_element_by_xpath('//*[@id="ap_email"]') 141 | break 142 | except NoSuchElementException: 143 | try: 144 | password_field = self.driver.find_element_by_xpath( 145 | '//*[@id="ap_password"]' 146 | ) 147 | break 148 | except NoSuchElementException: 149 | pass 150 | if time.time() > timeout: 151 | break 152 | 153 | def get_card(self, item, description, ctype, is_in, link, display_desc, true_price): 154 | """ Sift through a list item and extrace card data. """ 155 | 156 | # Get sold out tag if it exists 157 | try: 158 | sold_out = item.find_element_by_class_name("a-price-whole") 159 | sold_out = sold_out.text 160 | except NoSuchElementException: 161 | sold_out = "possibly" 162 | 163 | sku = link.split("=")[-1] 164 | if sold_out == '' or sold_out == None or sold_out == "possibly": 165 | in_stock = False 166 | self.printer.output(ctype, "OUT", "xxx", display_desc, "", self.stream_mode, "Newegg") 167 | else: 168 | in_stock = True 169 | print_desc = description.text[:20] 170 | if self.cards_to_find[ctype] is not None: 171 | if self.cards_to_find[ctype] and self.card_prices[ctype] > true_price: 172 | self.printer.output(ctype, "IN", true_price, 173 | print_desc, link, self.stream_mode, "NewEgg") 174 | buy = Purchase(self.driver, item, sku, "newegg") 175 | buy.make_purchase() 176 | else: 177 | self.printer.output(ctype, "EXP", true_price, 178 | print_desc, link, self.stream_mode, "NewEgg") 179 | 180 | def select_dropdown(self): 181 | drop = self.driver.find_elements_by_class_name("form-select") 182 | drop[1].find_element( 183 | By.XPATH, "//*[@value='96']/option[text()='96']").click 184 | print(drop[1].find_element( 185 | By.XPATH, "//*[@value='96']/option[text()='96']")) 186 | 187 | def loop_body(self, item): 188 | if item.text == '': 189 | return 190 | description = item.find_element_by_class_name("""//*[@id="search"]/div[1]/div/div[1]/div/span[3]/div[2]/div[6]/div/span/div/div/div[2]/div[2]/div/div[1]/h2/a/span""") 191 | offer_xpath = ( 192 | "//div[@id='aod-offer' and .//input[@name='submit.addToCart']] | " 193 | "//div[@id='aod-pinned-offer' and .//input[@name='submit.addToCart']]" 194 | ) 195 | # Get sold out tag if it exists 196 | try: 197 | sold_out = item.find_element_by_class_name("item-promo") 198 | sold_out = sold_out.text 199 | 200 | except NoSuchElementException: 201 | sold_out = "possibly" 202 | 203 | if description.text == '': 204 | if self.settings.save_logs: 205 | self.logger.info("Error, no description text for item {}".format(item)) 206 | return 207 | display_desc = self.get_chunks(description.text) 208 | is_in = True 209 | price = item.find_element_by_class_name("price-current") 210 | link = description.get_attribute("href") 211 | 212 | if "OUT" in sold_out or price.text == '': 213 | in_stock = False 214 | else: 215 | is_in = True 216 | true_price = float( 217 | re.sub(r'[^\d.]+', '', price.text.split('$')[1].strip())) 218 | 219 | for key in self.cards_to_find: 220 | if key in description.text: 221 | self.card_count[key] += 1 222 | self.total_prices[key] += 1 223 | self.avg_prices[key] = self.total_prices[key] / self.card_count[key] 224 | self.get_card(item, description, key, is_in, link, display_desc, true_price) 225 | break 226 | time.sleep(random.uniform(0, 1)) 227 | 228 | 229 | def validate_body(self, count, dt_string): 230 | if "" in self.driver.title: 231 | #notice = self.driver.find_elements_by_class_name( 232 | # "item-info") 233 | #stock = self.driver.find_elements_by_class_name( # s-result-item 234 | # "rush-component s-latency-cf-section") 235 | 236 | stock = self.driver.find_elements_by_class_name( # s-result-item 237 | "s-result-item") 238 | 239 | #queue = [] 240 | #queue = asyncio.Queue() 241 | 242 | #if not self.stream_mode: 243 | # if self.settings.show_progress_bar: 244 | # producers = [asyncio.create_task(self.loop_body(item)) for item in stock] 245 | # else: 246 | # producers = [asyncio.create_task(self.loop_body(item)) for item in stock] 247 | #else: 248 | # if self.settings.show_price_line: 249 | # self.printer.print_refresh(count, dt_string, self.old_prices, self.avg_prices) 250 | # producers = [asyncio.create_task(self.loop_body(item)) for item in stock] 251 | 252 | #await asyncio.gather(*producers) 253 | #await queue.join() 254 | 255 | if not self.stream_mode: 256 | if self.settings.show_progress_bar: 257 | for item in tqdm(stock): 258 | self.loop_body(item) 259 | else: 260 | for item in stock: 261 | self.loop_body(item) 262 | else: 263 | for item in stock: 264 | self.loop_body(item) 265 | self.printer.print_refresh(count, dt_string, self.old_prices, self.avg_prices) 266 | 267 | sleep_interval = 5+random.randrange(0, 1) 268 | elif self.driver.find_element_by_class_name("popup-close") != None: 269 | self.driver.find_element_by_class_name( 270 | "popup-close").click() 271 | 272 | def start(self): 273 | 274 | self.driver.get(self.url) 275 | count = 1 276 | try: 277 | while True: 278 | t0 = time.time() 279 | now = datetime.now() 280 | dt_string = now.strftime("%d/%m/%Y %H:%M:%S") 281 | 282 | delay = 3 # seconds 283 | try: 284 | myElem = WebDriverWait(self.driver, delay).until(EC.presence_of_element_located((By.CLASS_NAME, 'newegg-feedback'))) 285 | print("Page is ready!") 286 | except TimeoutException: 287 | print("Loading took too much time!") 288 | 289 | # Update prices 290 | for key in self.old_prices: 291 | self.old_prices[key] = self.avg_prices[key] 292 | t0 = time.time() 293 | now = datetime.now() 294 | dt_string = now.strftime("%d/%m/%Y %H:%M:%S") 295 | if self.settings.save_logs: 296 | msg = "" 297 | for key in self.avg_prices: 298 | msg += key + ': $' + str(self.avg_prices[key]) + ', ' 299 | msg += 'Iterations: ' + str(count) 300 | self.logger.info(msg) 301 | 302 | self.validate_body(count, dt_string) 303 | 304 | count += 1 305 | t1 = time.time() 306 | diff = t1 - t0 307 | print("diff: ", diff) 308 | if count % 3 == 0 and diff < 3: 309 | break 310 | self.driver.refresh() 311 | except KeyboardInterrupt: 312 | pass 313 | -------------------------------------------------------------------------------- /bots/bestbuy.py: -------------------------------------------------------------------------------- 1 | from selenium.common.exceptions import TimeoutException, WebDriverException 2 | from selenium.webdriver.support import expected_conditions as EC 3 | from selenium.common.exceptions import NoSuchElementException 4 | from selenium.common.exceptions import TimeoutException 5 | from selenium.webdriver.support.ui import WebDriverWait 6 | from selenium.webdriver.common.by import By 7 | from bots.purchase import Purchase 8 | #from botutils.paths import Paths 9 | from bots.browser import Browser 10 | from bots.printer import Printer 11 | from bots.logger import Logger 12 | from . import messenger as msg 13 | from datetime import datetime 14 | from .botutils.paths import Paths 15 | from colorama import init 16 | import logging 17 | import asyncio 18 | import time 19 | import sys 20 | import re 21 | 22 | 23 | 24 | #BEST_BUY_PDP_URL = "https://api.bestbuy.com/click/5592e2b895800000/{sku}/pdp" 25 | #BEST_BUY_CART_URL = "https://api.bestbuy.com/click/5592e2b895800000/{sku}/cart" 26 | 27 | #BEST_BUY_PDP_URL = "https://api.bestbuy.com/click/5592e2b895800000/{sku}/pdp" 28 | #BEST_BUY_CART_URL = "https://api.bestbuy.com/click/5592e2b895800000/{sku}/cart" 29 | 30 | #BEST_BUY_ADD_TO_CART_API_URL = "https://www.bestbuy.com/cart/api/v1/addToCart" 31 | #BEST_BUY_CHECKOUT_URL = "https://www.bestbuy.com/checkout/c/orders/{order_id}/" 32 | 33 | #DEFAULT_HEADERS = { 34 | # "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", 35 | # "accept-encoding": "gzip, deflate, br", 36 | # "accept-language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7", 37 | # "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36", 38 | # "origin": "https://www.bestbuy.com", 39 | #} 40 | 41 | 42 | class BestBuy(): 43 | 44 | def __init__(self, number, url, settings, debug=False): 45 | 46 | #load_dotenv() 47 | init(convert=True) 48 | 49 | fhandler = logging.FileHandler(filename='.\\bestBuyLog.log', mode='a') 50 | self.logger = Logger(fhandler) 51 | 52 | self.paths = Paths() 53 | #self.session = requests.Session() 54 | self.is_logged_in = False 55 | self.printer = Printer() 56 | self.debug = debug 57 | 58 | # General settings 59 | self.settings = settings 60 | self.url = url 61 | self.stream_mode = settings.stream_mode 62 | self.instock_only_mode = settings.instock_only_mode 63 | 64 | # What we're looking for 65 | self.cards_to_find = settings.cards_to_find 66 | self.card_prices = settings.card_prices 67 | 68 | # Login time 69 | self.login_at_start = settings.login_at_start 70 | 71 | # Statistics 72 | self.avg_prices = self.total_prices = self.card_count = self.old_prices = {} 73 | for key in self.cards_to_find: 74 | self.avg_prices[key] = self.total_prices[key] = self.card_count[key] = self.old_prices[key] = 0 75 | 76 | # BestBuy settings 77 | self.bb_info = settings.bb_info 78 | 79 | self.number = number 80 | self.sku_id = "" 81 | self.item_count = 0 82 | self.total_time = 0 83 | 84 | # Browser driver 85 | self.browser = Browser(settings) 86 | self.driver = self.browser.driver 87 | self.driver.get(url) 88 | 89 | def login(self): 90 | """ 91 | Log-in if not already. 92 | """ 93 | if self.bb_info["bb_auto_login"]: 94 | try: 95 | if self.settings.DEBUG_MODE and not self.debug["login_enabled"]: 96 | return 97 | if "Account" not in self.driver.find_element_by_class_name("account-button").text: 98 | self.is_logged_in = True 99 | return 100 | else: 101 | self.driver.get(self.paths.signin) 102 | WebDriverWait(self.driver, 2).until(EC.presence_of_element_located((By.XPATH, self.paths.pw_field))) 103 | if self.driver.find_element_by_xpath(self.paths.pw_field).get_attribute("value") != None and self.driver.find_element_by_xpath(self.paths.pw_field).get_attribute("value") != '': 104 | self.driver.find_element_by_xpath(self.paths.submit_login).click() 105 | else: 106 | self.driver.find_element_by_xpath('//*[@id="fld-e"]').send_keys( 107 | self.settings.bb_info["bb_email"] 108 | ) 109 | self.driver.find_element_by_xpath(self.paths.pw_field).send_keys( 110 | self.settings.bb_info["bb_password"] 111 | ) 112 | self.driver.find_element_by_xpath(self.paths.submit_login).click() 113 | WebDriverWait(self.driver, 6).until( 114 | lambda x: "Official Online Store" in self.driver.title 115 | ) 116 | self.is_logged_in = True 117 | except Exception as e: 118 | if self.settings.save_logs: 119 | self.logger.log_info("Login Failure: {}".format(e)) 120 | pass 121 | self.driver.get(self.url) 122 | 123 | def get_chunks(self, desc): 124 | """ 125 | Split the GPU description into easily parseable chunks. 126 | 127 | Properties: 128 | - desc: description string 129 | """ 130 | new_desc = desc.split("\n")[0] 131 | return new_desc 132 | 133 | def check_country_page(self): 134 | """ 135 | If you appear to be from a country outside the United States, 136 | then the country popup will appear. 137 | """ 138 | try: 139 | if "Select your Country" in self.driver.title: 140 | self.driver.find_element_by_xpath( 141 | self.paths.select_country).click() 142 | # driver.find_element_by_class_name("us-link").click() 143 | except NoSuchElementException: 144 | print("no block") 145 | 146 | def check_popup(self, driver): 147 | """ 148 | Check for a survey popup and exit out. 149 | 150 | Properties: 151 | - driver: webdriver 152 | """ 153 | try: 154 | if self.driver.find_element_by_id("survey_invite_no"): 155 | self.driver.find_element_by_id("survey_invite_no").click() 156 | except NoSuchElementException: 157 | print("no block") 158 | 159 | # Pass xpath of popup close button 160 | def close_popup(self, driver, path): 161 | """ 162 | Close arbitrary popup window. 163 | 164 | Properties: 165 | - driver: webdriver 166 | - path: string 167 | """ 168 | self.driver.find_element_by_xpath(path).click() 169 | 170 | def close_feedback_popup(self): 171 | """ 172 | Close feedback popup window. 173 | """ 174 | # Take the Survey 175 | try: 176 | if self.driver.find_element_by_id("survey_invite_no"): 177 | self.driver.find_element_by_id("survey_invite_no").click() 178 | except NoSuchElementException: 179 | print("No popup") 180 | 181 | 182 | def close_deals_popup(self): 183 | """ 184 | Close deals popup window. 185 | """ 186 | try: 187 | if self.driver.find_element_by_xpath(self.paths.close_deals_popup): 188 | self.driver.find_element_by_xpath( 189 | self.paths.close_deals_popup).click() 190 | except NoSuchElementException: 191 | print("no popup") 192 | 193 | def get_card(self, item, description, ctype, true_price, is_in, link): 194 | """ 195 | Get individual card object from page body. 196 | 197 | Properties: 198 | - item: card item 199 | - description: card description 200 | - ctype: card type / model 201 | - true_price: price of the card 202 | - is_in: whether card is in stock or not 203 | - link: the url of the card 204 | """ 205 | sku = link.split("=")[-1] 206 | if not is_in or "Sold Out" in description.split("\n"): 207 | in_stock = False 208 | self.printer.output(ctype, "OUT", "xxx", description, "", self.stream_mode, "BestBuy") 209 | else: 210 | in_stock = True 211 | print_desc = description[:20] 212 | if self.cards_to_find[ctype] is not None: 213 | if self.cards_to_find[ctype] and self.card_prices[ctype] > true_price: 214 | self.printer.output(ctype, "IN", true_price, 215 | print_desc, link, self.stream_mode, "BestBuy") 216 | if self.settings.send_messages: 217 | msg.send_message(self.number, "Card Found: " + str(link)) 218 | if self.bb_info["bb_auto_buy"]: 219 | buy = Purchase(self.driver, item, sku, "bestbuy", self.is_logged_in, self.logger, self.settings) 220 | buy.make_purchase_bb() 221 | else: 222 | self.printer.output(ctype, "EXP", true_price, 223 | print_desc, link, self.stream_mode, "BestBuy") 224 | 225 | async def loop_body(self, item): 226 | """ 227 | Loops over the card container to extract individual cards. 228 | 229 | Properties: 230 | - item: card item 231 | """ 232 | try: 233 | description = item.text 234 | link_item = item.find_element_by_class_name("sku-header") 235 | link = item.find_element_by_tag_name("a").get_attribute("href") 236 | 237 | parts = description.split("\n") 238 | cart_button = item.find_element_by_class_name("c-reviews-v4 ") 239 | nope = False 240 | 241 | if "Not yet reviewed" in cart_button.text: 242 | nope = True 243 | 244 | if len(parts) < 3 or "Sold Out" in parts[len(parts)-1] or nope: 245 | is_in = False 246 | else: 247 | is_in = True 248 | true_price = float( 249 | re.sub(r'[^\d.]+', '', description.split('$')[1].strip())) 250 | 251 | #if self.debug: 252 | # self.get_card(item, parts[0], "Test", true_price, is_in, link_item) 253 | 254 | for key in self.cards_to_find: 255 | if key in parts[0]: 256 | self.card_count[key] += 1 257 | self.total_prices[key] += 1 258 | self.avg_prices[key] = self.total_prices[key] / self.card_count[key] 259 | self.get_card(item, parts[0], key, true_price, is_in, link) 260 | break 261 | 262 | except NoSuchElementException as e: 263 | if self.settings.save_logs: 264 | self.logger.log_info(self.paths.error_loop.format(e)) 265 | pass 266 | except Exception as e: 267 | if self.settings.save_logs: 268 | self.logger.log_error(self.paths.error_loop.format(e)) 269 | pass 270 | 271 | async def validate_body(self, count, dt_string): 272 | """ 273 | Make sure there are actually cards in the body of the page. 274 | 275 | Properties: 276 | - count: current iteration count 277 | - dt_string: current time string 278 | """ 279 | try: 280 | if "" in self.driver.title: 281 | notice = self.driver.find_elements_by_class_name( 282 | "item-info") 283 | total = self.driver.find_element_by_id("main-results") 284 | stock = total.find_elements_by_class_name("sku-item") 285 | self.item_count = len(stock) 286 | queue = asyncio.Queue() 287 | 288 | if not self.stream_mode: 289 | if self.settings.show_progress_bar: 290 | producers = [asyncio.create_task(self.loop_body(item)) for item in stock] 291 | else: 292 | producers = [asyncio.create_task(self.loop_body(item)) for item in stock] 293 | else: 294 | if self.settings.show_price_line: 295 | self.printer.print_refresh(count, dt_string, self.old_prices, self.avg_prices) 296 | producers = [asyncio.create_task(self.loop_body(item)) for item in stock] 297 | 298 | await asyncio.gather(*producers) 299 | await queue.join() 300 | 301 | except NoSuchElementException as e: 302 | if self.settings.save_logs: 303 | self.logger.log_info(self.paths.error_loop.format(e)) 304 | pass 305 | except Exception as e: 306 | if self.settings.save_logs: 307 | self.logger.log_error(self.paths.error_loop.format(e)) 308 | pass 309 | 310 | def start(self): 311 | print("Started") 312 | count = 1 313 | try: 314 | self.check_country_page() 315 | self.close_deals_popup() 316 | self.close_feedback_popup() 317 | if self.login_at_start: 318 | self.login() 319 | while True: 320 | try: 321 | # Update prices 322 | for key in self.old_prices: 323 | self.old_prices[key] = self.avg_prices[key] 324 | t0 = time.time() 325 | now = datetime.now() 326 | dt_string = now.strftime("%d/%m/%Y %H:%M:%S") 327 | if self.settings.save_logs: 328 | msg = "" 329 | for key in self.avg_prices: 330 | msg += key + ': $' + str(self.avg_prices[key]) + ', ' 331 | msg += 'Iterations: ' + str(count) 332 | self.logger.log_info(msg) 333 | 334 | #self.validate_body(count, dt_string) 335 | asyncio.run(self.validate_body(count, dt_string)) 336 | page_num = 1 337 | try: 338 | self.driver.find_element_by_class_name("sku-list-page-next").click() 339 | page_num += 1 340 | #self.validate_body(count, dt_string) 341 | asyncio.run(self.validate_body(count, dt_string)) 342 | except (TimeoutException, WebDriverException) as e: 343 | self.driver.get(self.url) 344 | pass 345 | 346 | count += 1 347 | t1 = time.time() 348 | diff = t1 - t0 349 | self.total_time += diff 350 | time_per_card = diff / self.item_count 351 | if self.settings.show_refresh_time: 352 | print("BestBuy Refresh Time: ", diff, " sec. Avg card check time: ", time_per_card) 353 | #else: 354 | #spinner.next() 355 | if count % 3 == 0 and diff < 3: 356 | break 357 | # Prevent browser from slowing down by restarting it 358 | if self.total_time >= 1200: 359 | self.driver.close() 360 | self.browser = Browser(self.settings) 361 | self.driver = self.browser.driver 362 | self.driver.get(self.url) 363 | self.start() 364 | else: 365 | self.driver.refresh() 366 | except NoSuchElementException: 367 | self.logger.log_error( 368 | "Unable to find element. Refresh: " + str(count)) 369 | except KeyboardInterrupt: 370 | self.driver.close() 371 | sys.exit() 372 | -------------------------------------------------------------------------------- /bots/NewEgg.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.support import expected_conditions as EC 2 | from selenium.common.exceptions import NoSuchElementException 3 | from selenium.webdriver.support.wait import WebDriverWait 4 | from selenium.common.exceptions import TimeoutException 5 | from selenium.webdriver.common.by import By 6 | from colorama import Fore, Style, init 7 | from bots.botutils.paths import Paths 8 | from bots.textbox import TextEntry 9 | from bots.purchase import Purchase 10 | from bots import messenger as msg 11 | from bots.printer import Printer 12 | from bots.browser import Browser 13 | from bots.logger import Logger 14 | from bots.fakeai import FakeAI 15 | import scipy.interpolate as si 16 | from selenium import webdriver 17 | #from dotenv import load_dotenv 18 | from datetime import datetime 19 | #from bots.gmail import Gmail 20 | from time import sleep 21 | from tqdm import tqdm 22 | import numpy as np 23 | import requests 24 | import requests 25 | import logging 26 | import asyncio 27 | import random 28 | import time 29 | import re 30 | import os 31 | import re 32 | import sys 33 | 34 | MAX_TIMEOUT = 10 35 | 36 | class NewEgg(): 37 | 38 | def __init__(self, number, url, settings, debug): 39 | 40 | #load_dotenv() 41 | init(convert=True) 42 | fhandler = logging.FileHandler(filename='.\\neweggLog.log', mode='a') 43 | self.logger = Logger(fhandler) 44 | self.paths = Paths() 45 | #self.session = requests.Session() 46 | self.is_logged_in = False 47 | self.login_method_has_run = False 48 | self.DEBUG_MODE = settings.DEBUG_MODE 49 | self.debug = settings.debug 50 | self.printer = Printer() 51 | self.items_per_page = "&PageSize=90" 52 | self.settings = settings 53 | 54 | 55 | if settings is not None: 56 | self.MAX_TIMEOUT = self.settings.max_timeout 57 | else: 58 | self.MAX_TIMEOUT = MAX_TIMEOUT 59 | 60 | # General settings 61 | self.settings = settings 62 | self.url = url + self.items_per_page 63 | self.stream_mode = settings.stream_mode 64 | self.instock_only_mode = settings.instock_only_mode 65 | 66 | # What we're looking for 67 | self.cards_to_find = settings.cards_to_find 68 | self.card_prices = settings.card_prices 69 | 70 | # Login time 71 | self.login_at_start = settings.login_at_start 72 | 73 | # Statistics 74 | self.avg_prices = self.total_prices = self.card_count = self.old_prices = {} 75 | for key in self.cards_to_find: 76 | self.avg_prices[key] = \ 77 | self.total_prices[key] = \ 78 | self.card_count[key] = \ 79 | self.old_prices[key] = 0.0 80 | self.viewed_cards = {} 81 | 82 | # NewEgg settings 83 | self.ne_info = settings.ne_info 84 | 85 | self.number = number 86 | self.sku_id = sku_id = None 87 | self.item_count = 0 88 | 89 | # Browser driver 90 | self.browser = Browser(settings) 91 | self.driver = self.browser.driver 92 | self.driver.get(url) 93 | 94 | def close_popup(self, path): 95 | """ 96 | Close popup window. 97 | """ 98 | self.driver.find_element_by_xpath(path).click() 99 | 100 | def get_timeout(self, timeout=MAX_TIMEOUT): 101 | """ 102 | Get max page timeout. 103 | """ 104 | return time.time() + timeout 105 | 106 | def login(self): 107 | """ 108 | Log-in if not already. 109 | """ 110 | timeout = self.get_timeout() 111 | if not self.login_method_has_run: 112 | self.login_method_has_run = True 113 | while True: 114 | try: 115 | if self.DEBUG_MODE and not self.debug["login_enabled"]: 116 | return 117 | if self.driver.find_element_by_class_name(self.paths.nav_title).text == "Sign in / Register": 118 | self.driver.find_element_by_xpath(self.paths.signin_button).click() 119 | WebDriverWait(self.driver, 10).until( 120 | EC.presence_of_element_located((By.CLASS_NAME, "signin-title")) 121 | ) 122 | self.driver.find_element_by_id(self.paths.sign_email).send_keys(self.settings.ne_info['ne_email']) 123 | self.driver.find_element_by_id("signInSubmit").click() 124 | 125 | time.sleep(2) 126 | 127 | if self.driver.find_element_by_class_name("signin-title").text == "Security Code": 128 | self.enter_2fa() 129 | 130 | 131 | self.browser.driver_wait(By.ID, self.paths.input_pw) 132 | self.driver.find_element_by_id(self.paths.input_pw).send_keys(self.settings.ne_info['ne_password']) 133 | self.driver.find_element_by_id("signInSubmit").click() 134 | 135 | except NoSuchElementException as e: 136 | try: 137 | if self.driver.find_element_by_xpath(self.paths.human): 138 | self.captcha_try() 139 | except NoSuchElementException: 140 | pass 141 | if self.settings.save_logs: 142 | self.logger.log_info("Element not found in login(): {}".format(e)) 143 | pass 144 | except Exception as e: 145 | if self.settings.save_logs: 146 | self.logger.log_info("Error during login: {}".format(e)) 147 | pass 148 | self.is_logged_in = True 149 | if time.time() > timeout: 150 | break 151 | 152 | def captcha_try(self): 153 | """ 154 | Attempt to pass CAPTCHA challenge. 155 | """ 156 | while True: 157 | try: 158 | 159 | if self.driver.find_element_by_xpath(self.paths.human): 160 | element = self.driver.find_element_by_xpath(self.paths.captcha) 161 | # //*[@id="playAudio"] 162 | ai = FakeAI(self.driver) 163 | ai.move_cursor(element) 164 | except NoSuchElementException: 165 | pass 166 | except Exception as e: 167 | if self.settings.save_logs: 168 | self.logger.log_error("Error passing captcha: {}".format(e)) 169 | break 170 | 171 | def enter_2fa(self, screen=None): 172 | """ 173 | Resolve 2FA code. 174 | If running headless mode, a textbox will popup which you enter the code. 175 | 176 | Properties: 177 | - screen: (optional) tkinter popup text box 178 | """ 179 | self.printer.print_message(Fore.CYAN, "\n -------- PLEASE ENTER Newegg 2FA Code!!! -------- \n") 180 | while True: 181 | try: 182 | number = None 183 | 184 | if self.settings.ne_info["az_username"] or self.settings.ne_info["az_password"] == "yourInfoHere": 185 | self.printer.print_message(Fore.YELLOW, "UPDATE SETTINGS.JSON FILE") 186 | 187 | # Show popup for 2FA code if running headless 188 | if self.settings.headless_mode or self.DEBUG_MODE: 189 | popup = TextEntry() 190 | popup.showField() 191 | number = popup.getTextInput() 192 | 193 | val = "" 194 | 195 | if number != '' and number != None: 196 | element = self.driver.find_element_by_xpath(self.paths.two_factor).send_keys(number) 197 | 198 | try: 199 | txt_area = self.driver.find_element_by_class_name("form-v-code") 200 | val = txt_area.find_element_by_xpath(self.paths.tfa_value).get_attribute("value") 201 | except Exception as e: 202 | if self.settings.save_logs: 203 | self.logger.log_info("2FA text area not accessible, {}".format(e)) 204 | pass 205 | 206 | if val != None and val != "": 207 | self.driver.find_element_by_id("signInSubmit").click() 208 | break 209 | try: 210 | self.driver.find_element_by_class_name("signin-title") 211 | except NoSuchElementException: 212 | if self.driver.find_element_by_xpath(self.paths.human): 213 | self.captcha_try() 214 | break 215 | except NoSuchElementException: 216 | if self.driver.find_element_by_xpath(self.paths.human): 217 | self.captcha_try() 218 | break 219 | except Exception as e: 220 | if self.settings.save_logs: 221 | self.logger.log_info("Not on security code page anymore: {}".format(e)) 222 | break 223 | 224 | def get_chunks(self, desc): 225 | """ Break down description to extract only the first part. """ 226 | 227 | chunks, chunk_size = len(desc), len(desc)//4 228 | pts = [desc[i:i+chunk_size] for i in range(0, chunks, chunk_size)] 229 | return pts[0] 230 | 231 | def get_card(self, item, description, ctype, is_in, link, display_desc, true_price): 232 | """ 233 | Get individual card object from page body. 234 | 235 | Properties: 236 | - item: card item 237 | - description: card description 238 | - ctype: card type / model 239 | - is_in: whether card is in stock or not 240 | - link: the url of the card 241 | - display_desc: the card description to display 242 | - true_price: price of the card 243 | """ 244 | """ Sift through a list item and extrace card data. """ 245 | try: 246 | # Get sold out tag if it exists 247 | try: 248 | sold_out = item.find_element_by_class_name("item-promo").text 249 | except NoSuchElementException: 250 | try: 251 | sold_out = item.find_element_by_class_name("btn-mini").text 252 | except NoSuchElementException: 253 | sold_out = "possibly" 254 | 255 | sku = link.split("=")[-1] 256 | 257 | self.card_count[ctype] += 1.0 258 | self.total_prices[ctype] += true_price 259 | self.avg_prices[ctype] = self.total_prices[ctype] / self.card_count[ctype] 260 | if sold_out == 'OUT OF STOCK' or sold_out == 'View Details ': 261 | in_stock = False 262 | self.printer.output(ctype, "OUT", "xxx", display_desc, "", self.stream_mode, "Newegg") 263 | else: 264 | in_stock = True 265 | print_desc = description.text[:20] 266 | if description.text in self.viewed_cards and self.viewed_cards[str(description.text)]+600 > time.time(): 267 | pass 268 | elif description.text in self.viewed_cards and self.viewed_cards[str(description.text)]+600 < time.time(): 269 | del self.viewed_cards[str(description.text)] 270 | elif self.cards_to_find[ctype] is not None: 271 | if self.cards_to_find[ctype] and true_price < self.card_prices[ctype]: 272 | self.viewed_cards[str(description.text)] = time.time() 273 | self.printer.output(ctype, "IN", true_price, 274 | print_desc, link, self.stream_mode, "NewEgg") 275 | if self.settings.send_messages: 276 | msg.send_message(self.number, "Card Found: " + str(link)) 277 | if self.DEBUG_MODE and self.debug["test_payment_no_purchase"]: 278 | buy = Purchase(self.driver, item, sku, "newegg", self.is_logged_in, self.logger, self.settings) 279 | buy.make_purchase_ne() 280 | elif self.settings.ne_info["ne_auto_buy"]: 281 | buy = Purchase(self.driver, item, sku, "newegg", self.is_logged_in, self.logger, self.settings) 282 | buy.make_purchase_ne() 283 | else: 284 | self.printer.output(ctype, "EXP", true_price, 285 | print_desc, link, self.stream_mode, "NewEgg") 286 | 287 | except NoSuchElementException: 288 | try: 289 | if self.driver.find_element_by_xpath(self.paths.human): 290 | self.captcha_try() 291 | except NoSuchElementException: 292 | pass 293 | pass 294 | except Exception as e: 295 | if self.settings.save_logs: 296 | self.logger.log_info("Error in get_card: {}".format(e)) 297 | pass 298 | 299 | def select_dropdown(self): 300 | """ 301 | Select the maximum number of cards that can be displayed. 302 | """ 303 | timeout = self.get_timeout() 304 | while True: 305 | try: 306 | drop = self.driver.find_elements_by_class_name("form-select") 307 | drop[1].find_element( 308 | By.XPATH, self.paths.ne_dropdown).click 309 | break 310 | except NoSuchElementException: 311 | pass 312 | except Exception as e: 313 | if self.settings.save_logs: 314 | self.logger.log_info("Error in select_dropdown: {}".format(e)) 315 | pass 316 | 317 | if time.time() > timeout: 318 | break 319 | 320 | async def loop_body(self, item): 321 | """ 322 | Loops over the card container to extract individual cards. 323 | 324 | Properties: 325 | - item: card item 326 | """ 327 | try: 328 | 329 | if item.text == '': 330 | return 331 | 332 | description = item.find_element_by_class_name("item-title") 333 | 334 | # Get sold out tag if it exists 335 | try: 336 | sold_out = item.find_element_by_class_name("item-promo") 337 | sold_out = sold_out.text 338 | 339 | except NoSuchElementException: 340 | sold_out = "possibly" 341 | 342 | if description.text == '': 343 | if self.settings.save_logs: 344 | self.logger.log_info("Error, no description text for item {}".format(item)) 345 | return 346 | display_desc = self.get_chunks(description.text) 347 | is_in = True 348 | price = item.find_element_by_class_name("price-current") 349 | link = description.get_attribute("href") 350 | 351 | if "OUT" in sold_out or price.text == '': 352 | in_stock = False 353 | else: 354 | is_in = True 355 | true_price = float( 356 | re.sub(r'[^\d.]+', '', price.text.split('$')[1].strip())) 357 | 358 | for key in self.cards_to_find: 359 | if key in description.text: 360 | self.get_card(item, description, key, is_in, link, display_desc, true_price) 361 | break 362 | except NoSuchElementException: 363 | try: 364 | if self.driver.find_element_by_xpath(self.paths.human): 365 | self.captcha_try() 366 | except NoSuchElementException: 367 | pass 368 | pass 369 | except Exception as e: 370 | if self.settings.save_logs: 371 | self.logger.log_info(self.paths.error_loop.format(e)) 372 | pass 373 | 374 | 375 | async def validate_body(self, count, dt_string): 376 | """ 377 | Make sure there are actually cards in the body of the page. 378 | 379 | Properties: 380 | - count: current iteration count 381 | - dt_string: current time string 382 | """ 383 | try: 384 | if "" in self.driver.title: 385 | notice = self.driver.find_elements_by_class_name( 386 | "item-info") 387 | stock = self.driver.find_elements_by_class_name( 388 | "item-container") 389 | self.item_count = len(stock) 390 | 391 | queue = [] 392 | queue = asyncio.Queue() 393 | 394 | if not self.stream_mode: 395 | if self.settings.show_progress_bar: 396 | producers = [asyncio.create_task(self.loop_body(item)) for item in stock] 397 | else: 398 | producers = [asyncio.create_task(self.loop_body(item)) for item in stock] 399 | else: 400 | if self.settings.show_price_line: 401 | self.printer.print_refresh(count, dt_string, self.old_prices, self.avg_prices) 402 | producers = [asyncio.create_task(self.loop_body(item)) for item in stock] 403 | 404 | await asyncio.gather(*producers) 405 | await queue.join() 406 | 407 | sleep_interval = random.randrange(0, 2) 408 | if self.driver.find_element_by_class_name(self.paths.close_popup) != None: 409 | self.driver.find_element_by_class_name( 410 | self.paths.close_popup).click() 411 | 412 | except NoSuchElementException: 413 | try: 414 | if self.driver.find_element_by_xpath(self.paths.human): 415 | self.captcha_try() 416 | except NoSuchElementException: 417 | pass 418 | pass 419 | except Exception as e: 420 | if self.settings.save_logs: 421 | self.logger.log_info(self.paths.error_loop.format(e)) 422 | pass 423 | 424 | def start(self): 425 | 426 | self.driver.get(self.url + "&PageSize=96") 427 | 428 | 429 | #gm = Gmail(self.settings) 430 | 431 | self.login() 432 | self.select_dropdown() 433 | count = 1 434 | try: 435 | while True: 436 | t0 = time.time() 437 | now = datetime.now() 438 | dt_string = now.strftime("%d/%m/%Y %H:%M:%S") 439 | 440 | delay = 3 # seconds 441 | try: 442 | myElem = WebDriverWait(self.driver, delay).until(EC.presence_of_element_located((By.CLASS_NAME, 'newegg-feedback'))) 443 | print("Page is ready") 444 | except TimeoutException: 445 | print("Loading took too much time!") 446 | 447 | # Update prices 448 | for key in self.old_prices: 449 | self.old_prices[key] = self.avg_prices[key] 450 | t0 = time.time() 451 | now = datetime.now() 452 | dt_string = now.strftime("%d/%m/%Y %H:%M:%S") 453 | if self.settings.save_logs: 454 | msg = "" 455 | for key in self.avg_prices: 456 | msg = msg.join("{}: ${}, {}".format(key, str(self.avg_prices[key]), str(count))) 457 | msg = msg.join('Iterations: {}'.format(str(count))) 458 | self.logger.log_info(msg) 459 | 460 | asyncio.run(self.validate_body(count, dt_string)) 461 | 462 | count += 1 463 | t1 = time.time() 464 | diff = t1 - t0 465 | time_per_card = diff / (self.item_count + 1) 466 | if self.settings.show_refresh_time: 467 | print("Newegg Refresh Time: ", diff, " sec. Avg card check time: ", time_per_card) 468 | if count % 3 == 0 and diff < 3: 469 | break 470 | self.driver.refresh() 471 | except NoSuchElementException: 472 | try: 473 | if self.driver.find_element_by_xpath(self.paths.human): 474 | self.captcha_try() 475 | except NoSuchElementException: 476 | pass 477 | pass 478 | except Exception as e: 479 | if self.settings.save_logs: 480 | self.logger.log_info(self.paths.error_loop.format(e)) 481 | pass 482 | except KeyboardInterrupt: 483 | self.driver.close() 484 | sys.exit() 485 | -------------------------------------------------------------------------------- /bots/newegg.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.support import expected_conditions as EC 2 | from selenium.common.exceptions import NoSuchElementException 3 | from selenium.webdriver.support.wait import WebDriverWait 4 | from selenium.common.exceptions import TimeoutException 5 | from selenium.webdriver.common.by import By 6 | from colorama import Fore, Style, init 7 | from bots.botutils.paths import Paths 8 | from bots.textbox import TextEntry 9 | from bots.purchase import Purchase 10 | from bots import messenger as msg 11 | from bots.printer import Printer 12 | from bots.browser import Browser 13 | from bots.logger import Logger 14 | from bots.fakeai import FakeAI 15 | import scipy.interpolate as si 16 | from selenium import webdriver 17 | #from dotenv import load_dotenv 18 | from datetime import datetime 19 | #from bots.gmail import Gmail 20 | from time import sleep 21 | from tqdm import tqdm 22 | import numpy as np 23 | import requests 24 | import requests 25 | import logging 26 | import asyncio 27 | import random 28 | import time 29 | import re 30 | import os 31 | import re 32 | import sys 33 | 34 | MAX_TIMEOUT = 10 35 | 36 | class NewEgg(): 37 | 38 | def __init__(self, number, url, settings, debug): 39 | 40 | #load_dotenv() 41 | init(convert=True) 42 | fhandler = logging.FileHandler(filename='.\\neweggLog.log', mode='a') 43 | self.logger = Logger(fhandler) 44 | self.paths = Paths() 45 | #self.session = requests.Session() 46 | self.is_logged_in = False 47 | self.login_method_has_run = False 48 | self.DEBUG_MODE = settings.DEBUG_MODE 49 | self.debug = settings.debug 50 | self.printer = Printer() 51 | self.items_per_page = "&PageSize=90" 52 | self.settings = settings 53 | 54 | 55 | if settings is not None: 56 | self.MAX_TIMEOUT = self.settings.max_timeout 57 | else: 58 | self.MAX_TIMEOUT = MAX_TIMEOUT 59 | 60 | # General settings 61 | self.settings = settings 62 | self.url = url + self.items_per_page 63 | self.stream_mode = settings.stream_mode 64 | self.instock_only_mode = settings.instock_only_mode 65 | 66 | # What we're looking for 67 | self.cards_to_find = settings.cards_to_find 68 | self.card_prices = settings.card_prices 69 | 70 | # Login time 71 | self.login_at_start = settings.login_at_start 72 | 73 | # Statistics 74 | self.avg_prices = self.total_prices = self.card_count = self.old_prices = {} 75 | for key in self.cards_to_find: 76 | self.avg_prices[key] = \ 77 | self.total_prices[key] = \ 78 | self.card_count[key] = \ 79 | self.old_prices[key] = 0.0 80 | self.viewed_cards = {} 81 | 82 | # NewEgg settings 83 | self.ne_info = settings.ne_info 84 | 85 | self.number = number 86 | self.sku_id = sku_id = None 87 | self.item_count = 0 88 | 89 | # Browser driver 90 | self.browser = Browser(settings) 91 | self.driver = self.browser.driver 92 | self.driver.get(url) 93 | 94 | def close_popup(self, path): 95 | """ 96 | Close popup window. 97 | """ 98 | self.driver.find_element_by_xpath(path).click() 99 | 100 | def get_timeout(self, timeout=MAX_TIMEOUT): 101 | """ 102 | Get max page timeout. 103 | """ 104 | return time.time() + timeout 105 | 106 | def login(self): 107 | """ 108 | Log-in if not already. 109 | """ 110 | timeout = self.get_timeout() 111 | if not self.login_method_has_run: 112 | self.login_method_has_run = True 113 | while True: 114 | try: 115 | if self.DEBUG_MODE and not self.debug["login_enabled"]: 116 | return 117 | if self.driver.find_element_by_class_name(self.paths.nav_title).text == "Sign in / Register": 118 | self.driver.find_element_by_xpath(self.paths.signin_button).click() 119 | WebDriverWait(self.driver, 10).until( 120 | EC.presence_of_element_located((By.CLASS_NAME, "signin-title")) 121 | ) 122 | self.driver.find_element_by_id(self.paths.sign_email).send_keys(self.settings.ne_info['ne_email']) 123 | self.driver.find_element_by_id("signInSubmit").click() 124 | 125 | time.sleep(2) 126 | 127 | if self.driver.find_element_by_class_name("signin-title").text == "Security Code": 128 | self.enter_2fa() 129 | 130 | 131 | self.browser.driver_wait(By.ID, self.paths.input_pw) 132 | self.driver.find_element_by_id(self.paths.input_pw).send_keys(self.settings.ne_info['ne_password']) 133 | self.driver.find_element_by_id("signInSubmit").click() 134 | 135 | except NoSuchElementException as e: 136 | try: 137 | if self.driver.find_element_by_xpath(self.paths.human): 138 | self.captcha_try() 139 | except NoSuchElementException: 140 | pass 141 | if self.settings.save_logs: 142 | self.logger.log_info("Element not found in login(): {}".format(e)) 143 | pass 144 | except Exception as e: 145 | if self.settings.save_logs: 146 | self.logger.log_info("Error during login: {}".format(e)) 147 | pass 148 | self.is_logged_in = True 149 | if time.time() > timeout: 150 | break 151 | 152 | def captcha_try(self): 153 | """ 154 | Attempt to pass CAPTCHA challenge. 155 | """ 156 | while True: 157 | try: 158 | 159 | if self.driver.find_element_by_xpath(self.paths.human): 160 | element = self.driver.find_element_by_xpath(self.paths.captcha) 161 | # //*[@id="playAudio"] 162 | ai = FakeAI(self.driver) 163 | ai.move_cursor(element) 164 | except NoSuchElementException: 165 | pass 166 | except Exception as e: 167 | if self.settings.save_logs: 168 | self.logger.log_error("Error passing captcha: {}".format(e)) 169 | break 170 | 171 | def enter_2fa(self, screen=None): 172 | """ 173 | Resolve 2FA code. 174 | If running headless mode, a textbox will popup which you enter the code. 175 | 176 | Properties: 177 | - screen: (optional) tkinter popup text box 178 | """ 179 | self.printer.print_message(Fore.CYAN, "\n -------- PLEASE ENTER Newegg 2FA Code!!! -------- \n") 180 | while True: 181 | try: 182 | number = None 183 | 184 | if self.settings.ne_info["az_username"] or self.settings.ne_info["az_password"] == "yourInfoHere": 185 | self.printer.print_message(Fore.YELLOW, "UPDATE SETTINGS.JSON FILE") 186 | 187 | # Show popup for 2FA code if running headless 188 | if self.settings.headless_mode or self.DEBUG_MODE: 189 | popup = TextEntry() 190 | popup.showField() 191 | number = popup.getTextInput() 192 | 193 | val = "" 194 | 195 | if number != '' and number != None: 196 | element = self.driver.find_element_by_xpath(self.paths.two_factor).send_keys(number) 197 | 198 | try: 199 | txt_area = self.driver.find_element_by_class_name("form-v-code") 200 | val = txt_area.find_element_by_xpath(self.paths.tfa_value).get_attribute("value") 201 | except Exception as e: 202 | if self.settings.save_logs: 203 | self.logger.log_info("2FA text area not accessible, {}".format(e)) 204 | pass 205 | 206 | if val != None and val != "": 207 | self.driver.find_element_by_id("signInSubmit").click() 208 | break 209 | try: 210 | self.driver.find_element_by_class_name("signin-title") 211 | except NoSuchElementException: 212 | if self.driver.find_element_by_xpath(self.paths.human): 213 | self.captcha_try() 214 | break 215 | except NoSuchElementException: 216 | if self.driver.find_element_by_xpath(self.paths.human): 217 | self.captcha_try() 218 | break 219 | except Exception as e: 220 | if self.settings.save_logs: 221 | self.logger.log_info("Not on security code page anymore: {}".format(e)) 222 | break 223 | 224 | def get_chunks(self, desc): 225 | """ Break down description to extract only the first part. """ 226 | 227 | chunks, chunk_size = len(desc), len(desc)//4 228 | pts = [desc[i:i+chunk_size] for i in range(0, chunks, chunk_size)] 229 | return pts[0] 230 | 231 | def get_card(self, item, description, ctype, is_in, link, display_desc, true_price): 232 | """ 233 | Get individual card object from page body. 234 | 235 | Properties: 236 | - item: card item 237 | - description: card description 238 | - ctype: card type / model 239 | - is_in: whether card is in stock or not 240 | - link: the url of the card 241 | - display_desc: the card description to display 242 | - true_price: price of the card 243 | """ 244 | """ Sift through a list item and extrace card data. """ 245 | try: 246 | # Get sold out tag if it exists 247 | try: 248 | sold_out = item.find_element_by_class_name("item-promo").text 249 | except NoSuchElementException: 250 | try: 251 | sold_out = item.find_element_by_class_name("btn-mini").text 252 | except NoSuchElementException: 253 | sold_out = "possibly" 254 | 255 | sku = link.split("=")[-1] 256 | 257 | self.card_count[ctype] += 1.0 258 | self.total_prices[ctype] += true_price 259 | self.avg_prices[ctype] = self.total_prices[ctype] / self.card_count[ctype] 260 | if sold_out == 'OUT OF STOCK' or sold_out == 'View Details ': 261 | in_stock = False 262 | self.printer.output(ctype, "OUT", "xxx", display_desc, "", self.stream_mode, "Newegg") 263 | else: 264 | in_stock = True 265 | print_desc = description.text[:20] 266 | if description.text in self.viewed_cards and self.viewed_cards[str(description.text)]+600 > time.time(): 267 | pass 268 | elif description.text in self.viewed_cards and self.viewed_cards[str(description.text)]+600 < time.time(): 269 | del self.viewed_cards[str(description.text)] 270 | elif self.cards_to_find[ctype] is not None: 271 | if self.cards_to_find[ctype] and true_price < self.card_prices[ctype]: 272 | self.viewed_cards[str(description.text)] = time.time() 273 | self.printer.output(ctype, "IN", true_price, 274 | print_desc, link, self.stream_mode, "NewEgg") 275 | if self.settings.send_messages: 276 | msg.send_message(self.number, "Card Found: " + str(link)) 277 | if self.DEBUG_MODE and self.debug["test_payment_no_purchase"]: 278 | buy = Purchase(self.driver, item, sku, "newegg", self.is_logged_in, self.logger, self.settings) 279 | buy.make_purchase_ne() 280 | elif self.settings.ne_info["ne_auto_buy"]: 281 | buy = Purchase(self.driver, item, sku, "newegg", self.is_logged_in, self.logger, self.settings) 282 | buy.make_purchase_ne() 283 | else: 284 | self.printer.output(ctype, "EXP", true_price, 285 | print_desc, link, self.stream_mode, "NewEgg") 286 | 287 | except NoSuchElementException: 288 | try: 289 | if self.driver.find_element_by_xpath(self.paths.human): 290 | self.captcha_try() 291 | except NoSuchElementException: 292 | pass 293 | pass 294 | except Exception as e: 295 | if self.settings.save_logs: 296 | self.logger.log_info("Error in get_card: {}".format(e)) 297 | pass 298 | 299 | def select_dropdown(self): 300 | """ 301 | Select the maximum number of cards that can be displayed. 302 | """ 303 | timeout = self.get_timeout() 304 | while True: 305 | try: 306 | drop = self.driver.find_elements_by_class_name("form-select") 307 | drop[1].find_element( 308 | By.XPATH, self.paths.ne_dropdown).click 309 | break 310 | except NoSuchElementException: 311 | pass 312 | except Exception as e: 313 | if self.settings.save_logs: 314 | self.logger.log_info("Error in select_dropdown: {}".format(e)) 315 | pass 316 | 317 | if time.time() > timeout: 318 | break 319 | 320 | async def loop_body(self, item): 321 | """ 322 | Loops over the card container to extract individual cards. 323 | 324 | Properties: 325 | - item: card item 326 | """ 327 | try: 328 | 329 | if item.text == '': 330 | return 331 | 332 | description = item.find_element_by_class_name("item-title") 333 | 334 | # Get sold out tag if it exists 335 | try: 336 | sold_out = item.find_element_by_class_name("item-promo") 337 | sold_out = sold_out.text 338 | 339 | except NoSuchElementException: 340 | sold_out = "possibly" 341 | 342 | if description.text == '': 343 | if self.settings.save_logs: 344 | self.logger.log_info("Error, no description text for item {}".format(item)) 345 | return 346 | display_desc = self.get_chunks(description.text) 347 | is_in = True 348 | price = item.find_element_by_class_name("price-current") 349 | link = description.get_attribute("href") 350 | 351 | if "OUT" in sold_out or price.text == '': 352 | in_stock = False 353 | else: 354 | is_in = True 355 | true_price = float( 356 | re.sub(r'[^\d.]+', '', price.text.split('$')[1].strip())) 357 | 358 | for key in self.cards_to_find: 359 | if key in description.text: 360 | self.get_card(item, description, key, is_in, link, display_desc, true_price) 361 | break 362 | except NoSuchElementException: 363 | try: 364 | if self.driver.find_element_by_xpath(self.paths.human): 365 | self.captcha_try() 366 | except NoSuchElementException: 367 | pass 368 | pass 369 | except Exception as e: 370 | if self.settings.save_logs: 371 | self.logger.log_info(self.paths.error_loop.format(e)) 372 | pass 373 | 374 | 375 | async def validate_body(self, count, dt_string): 376 | """ 377 | Make sure there are actually cards in the body of the page. 378 | 379 | Properties: 380 | - count: current iteration count 381 | - dt_string: current time string 382 | """ 383 | try: 384 | if "" in self.driver.title: 385 | notice = self.driver.find_elements_by_class_name( 386 | "item-info") 387 | stock = self.driver.find_elements_by_class_name( 388 | "item-container") 389 | self.item_count = len(stock) 390 | 391 | queue = [] 392 | queue = asyncio.Queue() 393 | 394 | if not self.stream_mode: 395 | if self.settings.show_progress_bar: 396 | producers = [asyncio.create_task(self.loop_body(item)) for item in stock] 397 | else: 398 | producers = [asyncio.create_task(self.loop_body(item)) for item in stock] 399 | else: 400 | if self.settings.show_price_line: 401 | self.printer.print_refresh(count, dt_string, self.old_prices, self.avg_prices) 402 | producers = [asyncio.create_task(self.loop_body(item)) for item in stock] 403 | 404 | await asyncio.gather(*producers) 405 | await queue.join() 406 | 407 | sleep_interval = random.randrange(0, 2) 408 | if self.driver.find_element_by_class_name(self.paths.close_popup) != None: 409 | self.driver.find_element_by_class_name( 410 | self.paths.close_popup).click() 411 | 412 | except NoSuchElementException: 413 | try: 414 | if self.driver.find_element_by_xpath(self.paths.human): 415 | self.captcha_try() 416 | except NoSuchElementException: 417 | pass 418 | pass 419 | except Exception as e: 420 | if self.settings.save_logs: 421 | self.logger.log_info(self.paths.error_loop.format(e)) 422 | pass 423 | 424 | def start(self): 425 | 426 | self.driver.get(self.url + "&PageSize=96") 427 | 428 | 429 | #gm = Gmail(self.settings) 430 | 431 | self.login() 432 | self.select_dropdown() 433 | count = 1 434 | try: 435 | while True: 436 | t0 = time.time() 437 | now = datetime.now() 438 | dt_string = now.strftime("%d/%m/%Y %H:%M:%S") 439 | 440 | delay = 3 # seconds 441 | try: 442 | myElem = WebDriverWait(self.driver, delay).until(EC.presence_of_element_located((By.CLASS_NAME, 'newegg-feedback'))) 443 | print("Page is ready") 444 | except TimeoutException: 445 | print("Loading took too much time!") 446 | 447 | # Update prices 448 | for key in self.old_prices: 449 | self.old_prices[key] = self.avg_prices[key] 450 | t0 = time.time() 451 | now = datetime.now() 452 | dt_string = now.strftime("%d/%m/%Y %H:%M:%S") 453 | if self.settings.save_logs: 454 | msg = "" 455 | for key in self.avg_prices: 456 | msg = msg.join("{}: ${}, {}".format(key, str(self.avg_prices[key]), str(count))) 457 | msg = msg.join('Iterations: {}'.format(str(count))) 458 | self.logger.log_info(msg) 459 | 460 | asyncio.run(self.validate_body(count, dt_string)) 461 | 462 | count += 1 463 | t1 = time.time() 464 | diff = t1 - t0 465 | time_per_card = diff / (self.item_count + 1) 466 | if self.settings.show_refresh_time: 467 | print("Newegg Refresh Time: ", diff, " sec. Avg card check time: ", time_per_card) 468 | if count % 3 == 0 and diff < 3: 469 | break 470 | self.driver.refresh() 471 | except NoSuchElementException: 472 | try: 473 | if self.driver.find_element_by_xpath(self.paths.human): 474 | self.captcha_try() 475 | except NoSuchElementException: 476 | pass 477 | pass 478 | except Exception as e: 479 | if self.settings.save_logs: 480 | self.logger.log_info(self.paths.error_loop.format(e)) 481 | pass 482 | except KeyboardInterrupt: 483 | self.driver.close() 484 | sys.exit() 485 | -------------------------------------------------------------------------------- /bots/purchase.py: -------------------------------------------------------------------------------- 1 | 2 | from bots import messenger as msg 3 | from selenium import webdriver 4 | from selenium.webdriver.common.keys import Keys 5 | from selenium.webdriver.common.by import By 6 | from selenium.webdriver.support.ui import WebDriverWait 7 | from selenium.webdriver.chrome.options import Options 8 | from selenium.webdriver.support import expected_conditions as EC 9 | from selenium.common.exceptions import NoSuchElementException 10 | from selenium.webdriver.common.action_chains import ActionChains 11 | from selenium.webdriver.support.ui import Select 12 | import time 13 | from datetime import date 14 | from bots import messenger as msg 15 | import sys 16 | import imaplib 17 | import imapclient 18 | import email 19 | from email.header import decode_header 20 | import webbrowser 21 | import os 22 | 23 | NEWEGG_PDP_URL = "https://api.bestbuy.com/click/5592e2b895800000/{}/pdp" 24 | NEWEGG_CART_URL = "https://api.bestbuy.com/click/5592e2b895800000/{}/cart" 25 | NEWEGG_ADD_TO_CART = "https://secure.newegg.com/Shopping/AddToCart.aspx?Submit=ADD&ItemList={}" 26 | 27 | BEST_BUY_PDP_URL = "https://api.bestbuy.com/click/5592e2b895800000/{}/pdp" 28 | BEST_BUY_CART_URL = "https://api.bestbuy.com/click/5592e2b895800000/{}/cart" 29 | BEST_BUY_ADD_TO_CART_API_URL = "https://www.bestbuy.com/cart/api/v1/addToCart" 30 | BEST_BUY_CHECKOUT_URL = "https://www.bestbuy.com/checkout/c/orders/{}/" 31 | 32 | DEFAULT_MAX_TIMEOUT = 10 33 | 34 | BB_XPATH = { 35 | 36 | # Login Information 37 | "email_field": """//*[@id="fld-e"]""", 38 | "password_field": """//*[@id="fld-p1"]""", 39 | "guest_email_field": """//*[@id="user.emailAddress"]""", 40 | "guest_phone_field": """//*[@id="user.phone"]""", 41 | "checkout_login_btn": """/html/body/div[1]/div/section/main/div[2]/div[1]/div/div/div/div/form/div[3]/button""", 42 | 43 | # Shipping Information 44 | "shipping_v5": "//*[contains(@id, 'ui_address_5')]", 45 | "shipping_v2": "//*[contains(@id, 'ui_address_2')]", 46 | "firstname_field": """//*[@id="consolidatedAddresses.ui_address_{}.firstName"]""", 47 | "lastname_field": """//*[@id="consolidatedAddresses.ui_address_{}.lastName"]""", 48 | "street_address": """//*[@id="consolidatedAddresses.ui_address_{}.street"]""", 49 | "apartment": """//*[@id="consolidatedAddresses.ui_address_{}.street2"]""", 50 | "city": """//*[@id="consolidatedAddresses.ui_address_{}.city"]""", 51 | "state": "//select[@id='consolidatedAddresses.ui_address_{}.state']/option[text()='NC']", 52 | "zip": """//*[@id="consolidatedAddresses.ui_address_{}.zipcode"]""", 53 | "continue_to_billing": "//*[contains(text(), 'Continue to Payment Information')]", 54 | "switch_to_shipping": "//*[contains(text(), 'Switch to Shipping')]", 55 | 56 | # Payment Information 57 | "card_field": "//*[contains(@id, 'optimized-cc-card-number')]", 58 | "card_expiration_month": "expiration-month", 59 | "card_expiration_year": "expiration-year", 60 | "security_code": "credit-card-cvv", 61 | "place_order_btn": "//*[contains(text(), 'Place Your Order')]" 62 | } 63 | 64 | NE_XPATH = { 65 | 66 | # Add To Cart Btn 67 | "cart_btn": "//*[contains(text(), 'Add to cart')]", 68 | "view_cart_and_checkout": "//*[contains(text(), 'View Cart & Checkout')]", 69 | "secure_checkout": "//*[contains(text(), 'Secure Checkout')]", 70 | "cart": "//*[contains(text(), 'Shopping Cart')]", 71 | "guest_checkout_btn": "//*[contains(text(), 'Continue With Guest Checkout')]", 72 | 73 | 74 | # Sign-in 75 | "signin_label": "/html/body/div[5]/div/div[2]/div[2]/div/div/div[1]/div", 76 | "signin_email_field": """//*[@id="labeled-input-signEmail"]""", 77 | "password_field": """//*[contains(text(), 'Password')]""", 78 | "email_input": "/html/body/div[5]/div/div[2]/div[2]/div/div/div[1]/form[1]/div/div[1]/div/input", 79 | "signin_page_signin_btn": """//*[@id="signInSubmit"]""", 80 | 81 | # Shipping & Payment 82 | "continue_to_delivery": """//*[contains(text(), 'Continue to delivery')]""", 83 | "delivery_speed": """//*[@id="DhlSmartMail3To7Days"]""", 84 | "continue_to_payment": """//*[contains(text(), 'Continue to payment')]""", 85 | "cvv_field": "mask-cvv-4", 86 | "review_order_btn": """//*[contains(text(), 'Review your order')]""", 87 | "card_number_review_label": """//*[contains(text(), 'Card Number')]""", 88 | "card_number_review_input": "/html/body/div[6]/div/div[2]/div[2]/div[2]/input" 89 | 90 | } 91 | 92 | class Purchase(): 93 | def __init__(self, driver, item, sku, site, is_logged_in, logger, settings): 94 | self.driver = driver 95 | self.item = item 96 | self.sku = sku 97 | self.site = site 98 | self.is_logged_in = is_logged_in 99 | self.logger = logger 100 | self.settings = settings 101 | self.gmail_port = 993 102 | self.DEBUG_MODE = settings.DEBUG_MODE 103 | self.debug = settings.debug 104 | 105 | def make_purchase_bb(self): 106 | try: 107 | if self.sku is not None: 108 | self.driver.get(BEST_BUY_CART_URL.format(self.sku)) 109 | else: 110 | self.item.find_element_by_class_name("add-to-cart-button").click() 111 | try: 112 | self.driver_wait(By.CLASS_NAME, "c-modal-grid", max_wait=5) 113 | webdriver.ActionChains(self.driver).send_keys(Keys.ESCAPE).perform() 114 | except Exception: 115 | pass 116 | 117 | self.driver_wait(By.CLASS_NAME, "cart-link") 118 | time.sleep(1) 119 | self.driver.find_element_by_class_name("cart-link").click() 120 | 121 | self.driver_wait(By.ID, "cartApp", max_wait=5) 122 | time.sleep(1) 123 | #self.driver.find_element_by_xpath("""//*[@id="cartApp"]/div[2]/div[1]/div/div[2]/div[1]/section[2]/div/div/div[3]/div/div[1]/button""").click() 124 | self.driver.find_element_by_class_name("checkout-buttons__checkout").find_element_by_class_name("btn").click() 125 | 126 | #self.driver_wait(By.CLASS_NAME, "cia-signin") 127 | 128 | try: 129 | self.driver_wait(By.CLASS_NAME, "checkout-header__title", max_wait=4) 130 | self.driver.find_element_by_class_name("checkout-header__title") 131 | self.fill_shipping_info_bb() 132 | except Exception as e: 133 | pass 134 | try: 135 | self.driver_wait(By.CLASS_NAME, "cia-actual-full-page-wrapper", max_wait=5) 136 | if self.settings.checkout_as_guest: 137 | self.driver.find_element_by_class_name("guest").click() 138 | else: 139 | try: 140 | self.driver.find_element_by_xpath(BB_XPATH["email_field"]).send_keys(self.settings.bb_info["bb_email"]) 141 | except Exception: 142 | pass 143 | try: 144 | self.driver_wait(By.CLASS_NAME, "tb-input", max_wait=5) 145 | pw_field = self.driver.find_element_by_class_name("tb-input") 146 | if pw_field.get_attribute('value') == None or pw_field.get_attribute('value') == "": 147 | self.driver.find_element_by_class_name("tb-input").send_keys(self.settings.bb_info["bb_password"]) 148 | except Exception: 149 | pass 150 | self.driver.find_element_by_xpath(BB_XPATH["checkout_login_btn"]).click() 151 | self.select_shipping_bb() 152 | self.fill_shipping_info_bb() 153 | except Exception as e: 154 | pass 155 | 156 | self.fill_payment_info_bb() 157 | self.driver.find_element_by_xpath("/html/body/div[1]/main/div/div[2]/div[1]/div/div[1]/div[1]/section[2]/div/div/div[3]/div/div[1]/button").click() 158 | print("Item added to cart") 159 | except Exception as e: 160 | if self.settings.save_logs: 161 | self.logger.log_error("Exception Raised: {}".format(e)) 162 | pass 163 | 164 | def make_purchase_ne(self): 165 | try: 166 | self.driver.find_element_by_xpath(NE_XPATH["cart_btn"]).click() 167 | self.driver_wait(By.CLASS_NAME, "message-title", max_wait=4) 168 | 169 | # TODO: Remove / Fix driver wait 170 | time.sleep(1) 171 | self.driver.find_element_by_xpath(NE_XPATH["view_cart_and_checkout"]).click() 172 | 173 | self.driver_wait(By.XPATH, NE_XPATH["cart"], max_wait=4) 174 | self.driver.find_element_by_xpath(NE_XPATH["secure_checkout"]).click() 175 | #if not self.is_logged_in: 176 | if self.settings.checkout_as_guest: 177 | self.driver_wait(By.CLASS_NAME, "signin-title", max_wait=4) 178 | self.driver.find_element_by_xpath(NE_XPATH["guest_checkout_btn"]).click() 179 | else: 180 | self.signin_ne() 181 | self.enter_delivery_info_ne() 182 | self.enter_payment_info_ne() 183 | except Exception as e: 184 | if self.settings.save_logs: 185 | self.logger.log_error("Exception Raised: {}".format(e)) 186 | pass 187 | 188 | def signin_ne(self): 189 | try: 190 | self.driver_wait(By.CLASS_NAME, "signin-title", max_wait=4) 191 | if self.driver.find_element_by_id("labeled-input-signEmail").get_attribute("value") != self.settings.ne_info["ne_email"]: 192 | self.driver.find_element_by_id("labeled-input-signEmail").send_keys(self.settings.ne_info['ne_email']) 193 | # TODO: Remove / Replace with WebDriverWait 194 | time.sleep(1) 195 | self.driver.find_element_by_id("signInSubmit").click() 196 | 197 | 198 | try: 199 | self.driver_wait(By.XPATH, NE_XPATH["password_field"], max_wait=4) 200 | self.driver.find_element_by_id("labeled-input-password").send_keys(self.settings.ne_info['ne_password']) 201 | except Exception: 202 | pass 203 | 204 | self.driver.find_element_by_id("signInSubmit").click() 205 | #signin = self.driver.find_element_by_id("labeled-input-password").send_keys(self.settings.ne_info['ne_password']) 206 | 207 | self.driver.find_element_by_xpath(NE_XPATH["signin_page_signin_btn"]).click() 208 | except Exception: 209 | if self.settings.save_logs: 210 | self.logger.log_info("Failed during login") 211 | pass 212 | 213 | 214 | def enter_delivery_info_ne(self): 215 | # Continue to delivery 216 | try: 217 | self.driver_wait(By.CLASS_NAME, "checkout-step-action", max_wait=4) 218 | self.driver.find_element_by_xpath(NE_XPATH["continue_to_delivery"]).click() 219 | 220 | self.driver_wait(By.XPATH, NE_XPATH["continue_to_payment"], max_wait=4) 221 | self.driver.find_element_by_xpath(NE_XPATH["continue_to_payment"]).click() 222 | except Exception: 223 | if self.settings.save_logs: 224 | self.logger.log_info("Failed during delivery info entry") 225 | pass 226 | 227 | def driver_wait(self, id_type, text, max_wait=3): 228 | WebDriverWait(self.driver, max_wait).until( 229 | EC.presence_of_element_located((id_type, text)) 230 | ) 231 | 232 | def fill_shipping_info_bb(self): 233 | try: 234 | if self.settings.save_logs: 235 | msg = "Filling Shipping info, sku: {}".format(self.sku) 236 | self.logger.log_info(msg) 237 | 238 | self.driver_wait(By.CLASS_NAME, "streamlined__shipping", max_wait=4) 239 | #element = WebDriverWait(self.driver, 3).until( 240 | # EC.presence_of_element_located((By.CLASS_NAME, "streamlined__shipping")) 241 | #) 242 | pd_id = 2 243 | try: 244 | # BestBuy has multiple shipping forms?? 245 | ele = self.driver.find_element_by_xpath(BB_XPATH["shipping_v5"]) 246 | if ele is not None: 247 | pd_id = 5 248 | except NoSuchElementException as e: 249 | print("Already on shipping info page.") 250 | 251 | if self.driver.find_element_by_xpath(BB_XPATH["firstname_field"].format(pd_id)).get_attribute('value') == "": 252 | self.text_entry(self.driver.find_element_by_xpath(BB_XPATH["firstname_field"].format(pd_id)), self.settings.shipping_info["first_name"]) 253 | if self.driver.find_element_by_xpath(BB_XPATH["lastname_field"].format(pd_id)).get_attribute('value') == "": 254 | self.text_entry(self.driver.find_element_by_xpath(BB_XPATH["lastname_field"].format(pd_id)), self.settings.shipping_info["last_name"]) 255 | if self.driver.find_element_by_xpath(BB_XPATH["street_address"].format(pd_id)).get_attribute('value') == "": 256 | self.text_entry(self.driver.find_element_by_xpath(BB_XPATH["street_address"].format(pd_id)), self.settings.shipping_info["address"]) 257 | 258 | if self.settings.shipping_info["apartment"] == 'True': 259 | self.find_element_by_class_name("address-form__showAddress2Link").click() 260 | if self.driver.find_element_by_xpath(BB_XPATH["apartment"].format(pd_id)).get_attribute('value') == "": 261 | self.text_entry(self.driver.find_element_by_xpath(BB_XPATH["apartment"].format(pd_id)), self.settings.shipping_info["apartment"]) 262 | 263 | webdriver.ActionChains(self.driver).send_keys(Keys.ESCAPE).perform() 264 | 265 | if self.driver.find_element_by_xpath(BB_XPATH["city"].format(pd_id)).get_attribute('value') == "": 266 | self.text_entry(self.driver.find_element_by_xpath(BB_XPATH["city"].format(pd_id)), self.settings.shipping_info["city"]) 267 | self.driver.find_element_by_xpath(BB_XPATH["state"].format(pd_id)).click() 268 | if self.driver.find_element_by_xpath(BB_XPATH["zip"].format(pd_id)).get_attribute('value') == "": 269 | self.text_entry(self.driver.find_element_by_xpath(BB_XPATH["zip"].format(pd_id)), self.settings.shipping_info["zip_code"]) 270 | 271 | if self.settings.checkout_as_guest: 272 | if self.driver.find_element_by_xpath(BB_XPATH["guest_email_field"]).get_attribute('value') is None or "": 273 | self.driver.find_element_by_xpath(BB_XPATH["guest_email_field"]).send_keys(self.settings.bb_info["bb_email"]) 274 | if self.driver.find_element_by_xpath(BB_XPATH["guest_phone_field"]).get_attribute('value') is None or "": 275 | self.driver.find_element_by_xpath(BB_XPATH["guest_phone_field"]).send_keys(self.settings.shipping_info["phone_number"]) 276 | except Exception as e: 277 | if self.settings.save_logs: 278 | self.logger.log_error("Exception Raised: {}".format(e)) 279 | pass 280 | 281 | 282 | def enter_payment_info_ne(self): 283 | try: 284 | self.driver_wait(By.CLASS_NAME, "mask-cvv-4", max_wait=4) 285 | self.text_entry(self.driver.find_element_by_class_name(NE_XPATH["cvv_field"]), self.settings.payment_info["security_code"]) 286 | self.driver.find_element_by_xpath(NE_XPATH["review_order_btn"]).click() 287 | 288 | try: 289 | self.driver_wait(By.XPATH, NE_XPATH["card_number_review_label"], max_wait=4) 290 | self.text_entry(self.driver.find_element_by_xpath(NE_XPATH["card_number_review_input"]), self.settings.payment_info["security_code"]) 291 | self.driver.find_element_by_xpath(NE_XPATH["review_order_save"]) 292 | except Exception as e: 293 | if self.settings.save_logs: 294 | self.logger.log_info("Failed to find review order button{}".format(e)) 295 | pass 296 | 297 | # If just testing, don't purchase anything 298 | if self.DEBUG_MODE and not self.debug["purchase_enabled"]: 299 | if self.settings.save_log: 300 | self.logger.log_success("Demo Purchase Made") 301 | return 302 | 303 | try: 304 | self.driver_wait(By.ID, "btnCreditCard", max_wait=4) 305 | self.driver.find_element_by_id("btnCreditCard").click() 306 | self.logger.log_success("PURCHASE SUCCESSFUL") 307 | msg.send_message("GPU Order Placed, {}".format(self.driver.url)) 308 | except Exception as e: 309 | if self.settings.save_logs: 310 | self.logger.log_info("Failed to place order, {}".format(e)) 311 | 312 | except Exception: 313 | if self.settings.save_logs: 314 | self.logger.log_info("Failed to enter payment info") 315 | pass 316 | 317 | 318 | def fill_payment_info_bb(self): 319 | timeout = self.get_timeout() 320 | log_msg = "" 321 | while True: 322 | try: 323 | if self.settings.save_logs: 324 | msg = "Filling Payment info, sku: {}".format(self.sku) 325 | self.logger.log_info(msg) 326 | 327 | self.driver.find_element_by_xpath(BB_XPATH["continue_to_billing"]).click() 328 | self.driver_wait(By.CLASS_NAME, "payment__cc-label", max_wait=4) 329 | #WebDriverWait(self.driver, 10).until( 330 | # EC.presence_of_element_located((By.CLASS_NAME, "checkout__col")) 331 | # ) 332 | 333 | self.text_entry(self.driver.find_element_by_xpath(BB_XPATH["card_field"]), self.settings.payment_info["card_number"]) 334 | select = Select(self.driver.find_element_by_name(BB_XPATH["card_expiration_month"])) 335 | select.select_by_value(self.settings.payment_info["expiration_month"]) 336 | select = Select(self.driver.find_element_by_name(BB_XPATH["card_expiration_year"])) 337 | select.select_by_value(self.settings.payment_info["expiration_year"]) 338 | self.text_entry(self.driver.find_element_by_id(BB_XPATH["security_code"]), self.settings.payment_info["security_code"]) 339 | 340 | # If just testing, don't purchase anything 341 | if self.DEBUG_MODE and not self.debug["purchase_enabled"]: 342 | return 343 | 344 | self.driver.find_element_by_xpath(BB_XPATH["place_order_btn"]).click() 345 | print("Order Placed") 346 | self.logger.log_success("PURCHASE SUCCESSFUL") 347 | msg.send_message("GPU Order Placed, {}".format(self.driver.url)) 348 | 349 | if self.settings.save_logs: 350 | msg = "Purchase Made, sku: {}, {}".format(self.sku, str(date.today())) 351 | self.logger.log_info(msg) 352 | 353 | if not self.settings.purchase_multiple_items: 354 | sys.exit() 355 | except Exception as e: 356 | if self.settings.save_logs and log_msg != e: 357 | self.logger.log_error("Exception Raised during fill_payment_info_bb(): {}".format(e)) 358 | log_msg = e 359 | pass 360 | if time.time() > timeout: 361 | break 362 | 363 | def text_entry(self, element, text): 364 | #action.MoveByOffset(x+10, y+10).Perform(); #moves cursor to point (5,5) 365 | #action.MoveByOffset(10, 15).Perform(); #moves cursor to point (10,15) 366 | try: 367 | x = element.rect["x"] 368 | y = element.rect["y"] 369 | action = ActionChains(self.driver); 370 | action.move_to_element(element) 371 | action.click(element) 372 | action.send_keys(text) 373 | #for ch in text: 374 | # action.send_keys(element, ch) 375 | # time.sleep(0.001) 376 | action.perform() 377 | except Exception as e: 378 | if self.settings.save_logs: 379 | self.logger.log_info("Key entry failed: {}".format(e)) 380 | pass 381 | 382 | def get_timeout(self, timeout=DEFAULT_MAX_TIMEOUT): 383 | return time.time() + timeout 384 | 385 | def select_shipping_bb(self): 386 | try: 387 | self.driver.find_element_by_xpath(BB_XPATH["switch_to_shipping"]).click() 388 | 389 | except NoSuchElementException as e: 390 | if self.settings.save_logs: 391 | self.logger.log_error("Exception Raised: {}".format(e)) 392 | 393 | --------------------------------------------------------------------------------