def __init__(self, gpu, locale="en_us"): self.product_ids = [] self.cli_locale = locale.lower() self.locale = self.map_locales() self.session = requests.Session() self.gpu = gpu self.enabled = True try: self.gpu_long_name = GPU_DISPLAY_NAMES[gpu] except Exception as e: log.error("Invalid GPU name.") raise e adapter = HTTPAdapter(max_retries=Retry( total=10, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504], method_whitelist=["HEAD", "GET", "OPTIONS"], )) self.session.mount("https://", adapter) self.session.mount("http://", adapter) self.notification_handler = NotificationHandler() self.autobuy_handler = AutoBuy() log.info("Getting product IDs") self.get_product_ids() while len(self.product_ids) == 0: log.info( f"We have no product IDs for {self.gpu_long_name}, retrying until we get a product ID" ) self.get_product_ids() sleep(5)
def __init__(self, gpu, locale="en_us", test=False, interval=5): self.product_ids = set([]) self.cli_locale = locale.lower() self.locale = self.map_locales() self.session = requests.Session() self.gpu = gpu self.enabled = True self.auto_buy_enabled = False self.attempt = 0 self.started_at = datetime.now() self.test = test self.interval = interval self.gpu_long_name = GPU_DISPLAY_NAMES[gpu] # Disable auto_buy_enabled if the user does not provide a bool. if type(self.auto_buy_enabled) != bool: self.auto_buy_enabled = False adapter = TimeoutHTTPAdapter() self.session.mount("https://", adapter) self.session.mount("http://", adapter) self.notification_handler = NotificationHandler() self.get_product_ids()
def __init__(self, debug=False): self.notification_handler = NotificationHandler() self.driver = webdriver.Chrome(executable_path=binary_path, options=options, chrome_options=chrome_options) self.credit_card = {} try: if path.exists(CONFIG_PATH): with open(CONFIG_PATH) as json_file: config = json.load(json_file) username = config["username"] password = config["password"] self.credit_card["name"] = config["credit_card"]["name"] self.credit_card["number"] = config["credit_card"][ "number"] self.credit_card["cvv"] = config["credit_card"]["cvv"] self.credit_card["expiration_month"] = config[ "credit_card"]["expiration_month"] self.credit_card["expiration_year"] = config[ "credit_card"]["expiration_year"] except Exception as e: log.error( f"This is most likely an error with your {CONFIG_PATH} file.") raise e self.login(username, password)
def __init__(self, gpu, locale="en_us", test=False, interval=5): self.product_ids = set([]) self.cli_locale = locale.lower() self.locale = self.map_locales() self.session = requests.Session() self.gpu = gpu self.enabled = True self.auto_buy_enabled = False self.attempt = 0 self.started_at = datetime.now() self.test = test self.interval = interval self.gpu_long_name = GPU_DISPLAY_NAMES[gpu] # Disable auto_buy_enabled if the user does not provide a bool. if type(self.auto_buy_enabled) != bool: self.auto_buy_enabled = False adapter = TimeoutHTTPAdapter(max_retries=Retry( total=10, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504], method_whitelist=["HEAD", "GET", "OPTIONS"], )) self.session.mount("https://", adapter) self.session.mount("http://", adapter) self.notification_handler = NotificationHandler() self.get_product_ids() print(self.product_ids)
def decorator(*args, **kwargs): try: func(*args, **kwargs) except KeyboardInterrupt: pass except: NotificationHandler.send_notification(f"FairGame has crashed.") raise
def __init__(self, username, password, headless=False): self.notification_handler = NotificationHandler() if headless: enable_headless() self.driver = webdriver.Chrome(executable_path=binary_path, options=options) self.wait = WebDriverWait(self.driver, 10) self.username = username self.password = password self.login() time.sleep(3)
def __init__(self, username, password, debug=False): self.notification_handler = NotificationHandler() if not debug: chrome_options.add_argument("--headless") self.driver = webdriver.Chrome(executable_path=binary_path, options=options, chrome_options=chrome_options) self.wait = WebDriverWait(self.driver, 10) self.username = username self.password = password self.login() time.sleep(3)
def __init__(self, username, password, headless=False): self.notification_handler = NotificationHandler() if headless: enable_headless() self.driver = webdriver.Chrome(executable_path=binary_path, options=options) self.wait = WebDriverWait(self.driver, 10) self.username = username self.password = password self.driver.get(BASE_URL) if self.is_logged_in(): log.info("Already logged in") else: self.login() time.sleep(3)
def __init__(self, headless=False): self.notification_handler = NotificationHandler() if headless: enable_headless() options.add_argument(f"user-data-dir=.profile-amz") self.driver = webdriver.Chrome(executable_path=binary_path, options=options) self.wait = WebDriverWait(self.driver, 10) if path.exists(AUTOBUY_CONFIG_PATH): with open(AUTOBUY_CONFIG_PATH) as json_file: try: config = json.load(json_file) self.username = config["username"] self.password = config["password"] self.asin_list = config["asin_list"] self.amazon_website = config.get("amazon_website", "amazon.com") assert isinstance(self.asin_list, list) except Exception: raise InvalidAutoBuyConfigException( "amazon_config.json file not formatted properly.") else: log.error( "No config file found, see here on how to fix this: https://github.com/Hari-Nagarajan/nvidia-bot#amazon" ) exit(0) for key in AMAZON_URLS.keys(): AMAZON_URLS[key] = AMAZON_URLS[key].format(self.amazon_website) print(AMAZON_URLS) self.driver.get(AMAZON_URLS["BASE_URL"]) log.info("Waiting for home page.") self.check_if_captcha(self.wait_for_pages, HOME_PAGE_TITLES) if self.is_logged_in(): log.info("Already logged in") else: log.info("Lets log in.") selenium_utils.button_click_using_xpath( self.driver, '//*[@id="nav-link-accountList"]/div/span') log.info("Wait for Sign In page") self.check_if_captcha(self.wait_for_pages, SIGN_IN_TITLES) self.login() log.info("Waiting 15 seconds.") time.sleep( 15 ) # We can remove this once I get more info on the phone verification page.
def __init__(self, username, password, item_url, headless=False): self.notification_handler = NotificationHandler() if headless: enable_headless() h = hashlib.md5(item_url.encode()).hexdigest() options.add_argument(f"user-data-dir=.profile-amz-{h}") self.driver = webdriver.Chrome(executable_path=binary_path, options=options) self.wait = WebDriverWait(self.driver, 10) self.username = username self.password = password self.driver.get(BASE_URL) if self.is_logged_in(): log.info("Already logged in") else: self.login() time.sleep(15)
def test_notifications(disable_sound): notification_handler = NotificationHandler() enabled_handlers = ", ".join(notification_handler.enabled_handlers) message_time = datetime.now().strftime(TIME_FORMAT) notification_handler.send_notification( f"Beep boop. This is a test notification from FairGame. Sent {message_time}." ) log.info( f"A notification was sent to the following handlers: {enabled_handlers}" ) if not disable_sound: log.info("Testing notification sound...") notification_handler.play_notify_sound() time.sleep(2) # Prevent audio overlap log.info("Testing alert sound...") notification_handler.play_alarm_sound() time.sleep(2) # Prevent audio overlap log.info("Testing purchase sound...") notification_handler.play_purchase_sound() else: log.info("Local sounds disabled for this test.") # Give the notifications a chance to get out before we quit time.sleep(5)
class Amazon: def __init__(self, headless=False): self.notification_handler = NotificationHandler() if headless: enable_headless() options.add_argument(f"user-data-dir=.profile-amz") self.driver = webdriver.Chrome(executable_path=binary_path, options=options) self.wait = WebDriverWait(self.driver, 10) if path.exists(AUTOBUY_CONFIG_PATH): with open(AUTOBUY_CONFIG_PATH) as json_file: try: config = json.load(json_file) self.username = config["username"] self.password = config["password"] self.asin_list = config["asin_list"] self.amazon_website = config.get("amazon_website", "amazon.com") assert isinstance(self.asin_list, list) except Exception: raise InvalidAutoBuyConfigException( "amazon_config.json file not formatted properly.") else: raise InvalidAutoBuyConfigException( "Missing amazon_config.json file.") for key in AMAZON_URLS.keys(): AMAZON_URLS[key] = AMAZON_URLS[key].format(self.amazon_website) print(AMAZON_URLS) self.driver.get(AMAZON_URLS["BASE_URL"]) if self.is_logged_in(): log.info("Already logged in") else: log.info("Lets log in.") selenium_utils.button_click_using_xpath( self.driver, '//*[@id="nav-link-accountList"]/div/span') selenium_utils.wait_for_any_title(self.driver, SIGN_IN_TITLES) self.login() log.info("Waiting 15 seconds.") time.sleep( 15 ) # We can remove this once I get more info on the phone verification page. def is_logged_in(self): try: text = wait_for_element(self.driver, "nav-link-accountList").text return "Hello, Sign in" not in text except Exception: return False def login(self): try: log.info("Email") self.driver.find_element_by_xpath('//*[@id="ap_email"]').send_keys( self.username + Keys.RETURN) except: log.info("Email not needed.") pass log.info("Password") self.driver.find_element_by_xpath( '//input[@name="rememberMe"]').click() self.driver.find_element_by_xpath('//*[@id="ap_password"]').send_keys( self.password + Keys.RETURN) log.info(f"Logged in as {self.username}") def run_item(self, delay=3, test=False): log.info("Checking stock for items.") while not self.something_in_stock(): time.sleep(delay) self.notification_handler.send_notification( "Your items on Amazon.com were found!") self.checkout(test=test) def something_in_stock(self): params = {"anticache": str(secrets.token_urlsafe(32))} for x in range(len(self.asin_list)): params[f"ASIN.{x + 1}"] = self.asin_list[x] params[f"Quantity.{x + 1}"] = 1 f = furl(AMAZON_URLS["CART_URL"]) f.set(params) self.driver.get(f.url) selenium_utils.wait_for_any_title(self.driver, ADD_TO_CART_TITLES) if self.driver.find_elements_by_xpath('//td[@class="price item-row"]'): log.info("One or more items in stock!") return True else: return False def wait_for_cart_page(self): selenium_utils.wait_for_any_title(self.driver, SHOPING_CART_TITLES) log.info("On cart page.") def wait_for_pyo_page(self): selenium_utils.wait_for_any_title(self.driver, CHECKOUT_TITLES + SIGN_IN_TITLES) if self.driver.title in SIGN_IN_TITLES: log.info("Need to sign in again") self.login() def finalize_order_button(self, test, retry=0): button_xpaths = [ '//*[@id="bottomSubmitOrderButtonId"]/span/input', '//*[@id="placeYourOrder"]/span/input', '//*[@id="submitOrderButtonId"]/span/input', '//input[@name="placeYourOrder1"]', ] button = None for button_xpath in button_xpaths: try: if (self.driver.find_element_by_xpath( button_xpath).is_displayed() and self.driver.find_element_by_xpath( button_xpath).is_enabled()): button = self.driver.find_element_by_xpath(button_xpath) except NoSuchElementException: log.debug(f"{button_xpath}, lets try a different one.") if button: log.info(f"Clicking Button: {button}") if not test: button.click() return else: if retry < 3: log.info("Couldn't find button. Lets retry in a sec.") time.sleep(5) self.finalize_order_button(test, retry + 1) else: log.info( "Couldn't find button after 3 retries. Open a GH issue for this." ) def wait_for_order_completed(self, test): if not test: selenium_utils.wait_for_any_title(self.driver, ORDER_COMPLETE_TITLES) else: log.info( "This is a test, so we don't need to wait for the order completed page." ) def checkout(self, test): log.info("Clicking continue.") self.driver.find_element_by_xpath('//input[@value="add"]').click() log.info("Waiting for Cart Page") self.wait_for_cart_page() log.info("clicking checkout.") self.driver.find_element_by_xpath( '//*[@id="sc-buy-box-ptc-button"]/span/input').click() log.info("Waiting for Place Your Order Page") self.wait_for_pyo_page() log.info("Finishing checkout") self.finalize_order_button(test) log.info("Waiting for Order completed page.") self.wait_for_order_completed(test) log.info("Order Placed.")
def testnotification(): notification_handler = NotificationHandler() notification_handler.send_notification(f"Notifications Test")
class Amazon: def __init__(self, headless=False): self.notification_handler = NotificationHandler() if headless: enable_headless() options.add_argument(f"user-data-dir=.profile-amz") self.driver = webdriver.Chrome(executable_path=binary_path, options=options) self.wait = WebDriverWait(self.driver, 10) if path.exists(AUTOBUY_CONFIG_PATH): with open(AUTOBUY_CONFIG_PATH) as json_file: try: config = json.load(json_file) self.username = config["username"] self.password = config["password"] self.asin_list = config["asin_list"] self.amazon_website = config.get("amazon_website", "amazon.com") assert isinstance(self.asin_list, list) except Exception: raise InvalidAutoBuyConfigException( "amazon_config.json file not formatted properly.") else: log.error( "No config file found, see here on how to fix this: https://github.com/Hari-Nagarajan/nvidia-bot#amazon" ) exit(0) for key in AMAZON_URLS.keys(): AMAZON_URLS[key] = AMAZON_URLS[key].format(self.amazon_website) print(AMAZON_URLS) self.driver.get(AMAZON_URLS["BASE_URL"]) log.info("Waiting for home page.") self.check_if_captcha(self.wait_for_pages, HOME_PAGE_TITLES) if self.is_logged_in(): log.info("Already logged in") else: log.info("Lets log in.") selenium_utils.button_click_using_xpath( self.driver, '//*[@id="nav-link-accountList"]/div/span') log.info("Wait for Sign In page") self.check_if_captcha(self.wait_for_pages, SIGN_IN_TITLES) self.login() log.info("Waiting 15 seconds.") time.sleep( 15 ) # We can remove this once I get more info on the phone verification page. def is_logged_in(self): try: text = wait_for_element(self.driver, "nav-link-accountList").text return "Hello, Sign in" not in text except Exception: return False def login(self): try: log.info("Email") self.driver.find_element_by_xpath('//*[@id="ap_email"]').send_keys( self.username + Keys.RETURN) except: log.info("Email not needed.") pass log.info("Remember me checkbox") selenium_utils.button_click_using_xpath(self.driver, '//*[@name="rememberMe"]') log.info("Password") self.driver.find_element_by_xpath('//*[@id="ap_password"]').send_keys( self.password + Keys.RETURN) log.info(f"Logged in as {self.username}") def run_item(self, delay=3, test=False): log.info("Checking stock for items.") while not self.something_in_stock(): time.sleep(delay) self.notification_handler.send_notification( "Your items on Amazon.com were found!") self.checkout(test=test) def something_in_stock(self): params = {"anticache": str(secrets.token_urlsafe(32))} for x in range(len(self.asin_list)): params[f"ASIN.{x + 1}"] = self.asin_list[x] params[f"Quantity.{x + 1}"] = 1 f = furl(AMAZON_URLS["CART_URL"]) f.set(params) self.driver.get(f.url) self.check_if_captcha(self.wait_for_pages, ADD_TO_CART_TITLES) if self.driver.find_elements_by_xpath('//td[@class="price item-row"]'): log.info("One or more items in stock!") return True else: return False def get_captcha_help(self): if not self.on_captcha_page(): log.info("Not on captcha page.") return try: log.info("Stuck on a captcha... Lets try to solve it.") captcha = AmazonCaptcha.from_webdriver(self.driver) solution = captcha.solve() log.info(f"The solution is: {solution}") if solution == "Not solved": log.info( f"Failed to solve, lets reload and get a new captcha.") self.driver.refresh() time.sleep(5) self.get_captcha_help() else: self.driver.find_element_by_xpath( '//*[@id="captchacharacters"]').send_keys(solution + Keys.RETURN) except Exception as e: log.debug(e) log.info("Error trying to solve captcha. Refresh and retry.") self.driver.refresh() time.sleep(5) def on_captcha_page(self): try: if self.driver.title in CAPTCHA_PAGE_TITLES: return True if self.driver.find_element_by_xpath( '//form[@action="/errors/validateCaptcha"]'): return True except Exception: pass return False def check_if_captcha(self, func, args): try: func(args) except Exception as e: log.debug(str(e)) if self.on_captcha_page(): self.get_captcha_help() func(args, t=300) else: log.debug(self.driver.title) log.error( f"An error happened, please submit a bug report including a screenshot of the page the " f"selenium browser is on. There may be a file saved at: amazon-{func.__name__}.png" ) self.driver.save_screenshot(f"amazon-{func.__name__}.png") time.sleep(60) self.driver.close() raise e def wait_for_pages(self, page_titles, t=30): log.debug(f"wait_for_pages({page_titles}, {t})") selenium_utils.wait_for_any_title(self.driver, page_titles, t) def wait_for_pyo_page(self): self.check_if_captcha(self.wait_for_pages, CHECKOUT_TITLES + SIGN_IN_TITLES) if self.driver.title in SIGN_IN_TITLES: log.info("Need to sign in again") self.login() def finalize_order_button(self, test, retry=0): button_xpaths = [ '//*[@id="bottomSubmitOrderButtonId"]/span/input', '//*[@id="placeYourOrder"]/span/input', '//*[@id="submitOrderButtonId"]/span/input', '//input[@name="placeYourOrder1"]', ] button = None for button_xpath in button_xpaths: try: if (self.driver.find_element_by_xpath( button_xpath).is_displayed() and self.driver.find_element_by_xpath( button_xpath).is_enabled()): button = self.driver.find_element_by_xpath(button_xpath) except NoSuchElementException: log.debug(f"{button_xpath}, lets try a different one.") if button: log.info(f"Clicking Button: {button}") if not test: button.click() return else: if retry < 3: log.info("Couldn't find button. Lets retry in a sec.") time.sleep(5) self.finalize_order_button(test, retry + 1) else: log.info( "Couldn't find button after 3 retries. Open a GH issue for this." ) def wait_for_order_completed(self, test): if not test: self.check_if_captcha(self.wait_for_pages, ORDER_COMPLETE_TITLES) else: log.info( "This is a test, so we don't need to wait for the order completed page." ) def checkout(self, test): log.info("Clicking continue.") self.driver.find_element_by_xpath('//input[@value="add"]').click() log.info("Waiting for Cart Page") self.check_if_captcha(self.wait_for_pages, SHOPING_CART_TITLES) log.info("clicking checkout.") try: self.driver.find_element_by_xpath( '//*[@id="sc-buy-box-ptc-button"]/span/input').click() except Exception as e: log.debug(e) log.info("Failed to checkout. Returning to stock check.") self.run_item(test=test) log.info("Waiting for Place Your Order Page") self.wait_for_pyo_page() log.info("Finishing checkout") self.finalize_order_button(test) log.info("Waiting for Order completed page.") self.wait_for_order_completed(test) log.info("Order Placed.")
def __init__(self, gpu, locale="en_us", test=False): self.product_ids = set([]) self.cli_locale = locale.lower() self.locale = self.map_locales() self.session = requests.Session() self.gpu = gpu self.enabled = True self.auto_buy_enabled = False self.attempt = 0 self.started_at = datetime.now() self.test = test self.gpu_long_name = GPU_DISPLAY_NAMES[gpu] if path.exists(AUTOBUY_CONFIG_PATH): with open(AUTOBUY_CONFIG_PATH) as json_file: try: self.config = json.load(json_file) except Exception as e: log.error( "Your `autobuy_config.json` file is not valid json.") raise e if self.has_valid_creds(): self.nvidia_login = self.config["NVIDIA_LOGIN"] self.nvidia_password = self.config["NVIDIA_PASSWORD"] self.auto_buy_enabled = self.config["FULL_AUTOBUY"] self.cvv = self.config.get("CVV") self.interval = int(self.config.get("INTERVAL", 5)) else: raise InvalidAutoBuyConfigException(self.config) else: log.info("No Autobuy creds found.") # Disable auto_buy_enabled if the user does not provide a bool. if type(self.auto_buy_enabled) != bool: self.auto_buy_enabled = False adapter = TimeoutHTTPAdapter(max_retries=Retry( total=10, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504], method_whitelist=["HEAD", "GET", "OPTIONS"], )) self.session.mount("https://", adapter) self.session.mount("http://", adapter) self.notification_handler = NotificationHandler() log.info("Opening Webdriver") chrome_options = webdriver.ChromeOptions() chrome_options.add_experimental_option("excludeSwitches", ["enable-logging"]) self.driver = webdriver.Chrome(executable_path=binary_path, options=options, chrome_options=chrome_options) self.sign_in() selenium_utils.add_cookies_to_session_from_driver( self.driver, self.session) log.info("Adding driver cookies to session") log.info("Getting product IDs") self.token_data = self.get_nvidia_access_token() self.payment_option = self.get_payment_options() if not self.payment_option.get("id") or not self.cvv: log.error( "No payment option on account or missing CVV. Disable Autobuy") self.auto_buy_enabled = False else: log.debug(self.payment_option) self.ext_ip = self.get_ext_ip() if not self.auto_buy_enabled: log.info("Closing webdriver") self.driver.close() self.get_product_ids() while len(self.product_ids) == 0: log.info( f"We have no product IDs for {self.gpu_long_name}, retrying until we get a product ID" ) self.get_product_ids() sleep(5)
class Evga: def __init__(self, debug=False): self.notification_handler = NotificationHandler() self.driver = webdriver.Chrome(executable_path=binary_path, options=options, chrome_options=chrome_options) self.credit_card = {} try: if path.exists(CONFIG_PATH): with open(CONFIG_PATH) as json_file: config = json.load(json_file) username = config["username"] password = config["password"] self.credit_card["name"] = config["credit_card"]["name"] self.credit_card["number"] = config["credit_card"][ "number"] self.credit_card["cvv"] = config["credit_card"]["cvv"] self.credit_card["expiration_month"] = config[ "credit_card"]["expiration_month"] self.credit_card["expiration_year"] = config[ "credit_card"]["expiration_year"] except Exception as e: log.error( f"This is most likely an error with your {CONFIG_PATH} file.") raise e self.login(username, password) def login(self, username, password): """ We're just going to attempt to load cookies, else enter the user info and let the user handle the captcha :param username: :param password: :return: """ self.driver.execute_cdp_cmd( "Network.setUserAgentOverride", { "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.53 Safari/537.36" }, ) self.driver.execute_script( "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})" ) if path.isfile("evga-cookies.pkl"): # check for cookies file self.driver.get("https://www.evga.com") selenium_utils.wait_for_page( self.driver, "EVGA - Intelligent Innovation - Official Website", 300) cookies = pickle.load(open("evga-cookies.pkl", "rb")) for cookie in cookies: self.driver.add_cookie(cookie) self.driver.get("https://www.evga.com") selenium_utils.wait_for_page( self.driver, "EVGA - Intelligent Innovation - Official Website", 300) if (len(self.driver.find_elements_by_id("svg-login")) > 0): # cookies did not provide logged in state self.driver.get(LOGIN_URL) selenium_utils.wait_for_page(self.driver, "EVGA - Intelligent Innovation") selenium_utils.field_send_keys(self.driver, "evga_login", username) selenium_utils.field_send_keys(self.driver, "password", password) log.info("Go do the captcha and log in") selenium_utils.wait_for_page( self.driver, "EVGA - Intelligent Innovation - Official Website", 300) pickle.dump(self.driver.get_cookies(), open("evga-cookies.pkl", "wb")) # save cookies log.info("Logged in!") def buy(self, delay=5, test=False, model=""): selector = '//a[@id="LFrame_btnAddToCart"]' associate_code = "&associatecode=2QME1VF65K9ZY8B" if test: model_name = "test" url = "https://www.evga.com/products/product.aspx?pn=08G-P4-3289-KR" self.driver.get(url + associate_code) selenium_utils.wait_for_page( self.driver, "EVGA - Products - EVGA GeForce RTX 2080 SUPER FTW3 HYDRO COPPER GAMING, 08G-P4-3289-KR, 8GB GDDR6, RGB LED, iCX2 Technology, Metal Backplate - 08G-P4-3289-KR", ) else: models = { "ftw3": [ "https://www.evga.com/products/product.aspx?pn=10G-P5-3895-KR", "EVGA - Products - EVGA GeForce RTX 3080 FTW3 GAMING, 10G-P5-3895-KR, 10GB GDDR6X, iCX3 Technology, ARGB LED, Metal Backplate - 10G-P5-3895-KR", ], "xc3": [ "https://www.evga.com/products/product.aspx?pn=10G-P5-3883-KR", "EVGA - Products - EVGA GeForce RTX 3080 XC3 GAMING, 10G-P5-3883-KR, 10GB GDDR6X, iCX3 Cooling, ARGB LED, Metal Backplate - 10G-P5-3883-KR", ], "xc3ultra": [ "https://www.evga.com/products/product.aspx?pn=10G-P5-3885-KR", "EVGA - Products - EVGA GeForce RTX 3080 XC3 ULTRA GAMING, 10G-P5-3885-KR, 10GB GDDR6X, iCX3 Cooling, ARGB LED, Metal Backplate - 10G-P5-3885-KR", ], "xc3black": [ "https://www.evga.com/products/product.aspx?pn=10G-P5-3881-KR", "EVGA - Products - EVGA GeForce RTX 3080 XC3 BLACK GAMING, 10G-P5-3881-KR, 10GB GDDR6X, iCX3 Cooling, ARGB LED - 10G-P5-3881-KR", ], "ftw3ultra": [ "https://www.evga.com/products/product.aspx?pn=10G-P5-3897-KR", "EVGA - Products - EVGA GeForce RTX 3080 FTW3 ULTRA GAMING, 10G-P5-3897-KR, 10GB GDDR6X, iCX3 Technology, ARGB LED, Metal Backplate - 10G-P5-3897-KR", ], "any": [ "https://www.evga.com/products/productlist.aspx?type=0&family=GeForce+30+Series+Family&chipset=RTX+3080", "EVGA - Products - Graphics - GeForce 30 Series Family - RTX 3080", ], } if model: self.driver.get(models[model][0] + associate_code) selenium_utils.wait_for_page(self.driver, models[model][1]) else: model = "any" selector = '//input[@class="btnBigAddCart"]' self.driver.get(models["any"][0] + associate_code) selenium_utils.wait_for_page(self.driver, models["any"][1]) # Check for stock log.info("On GPU Page") atc_buttons = self.driver.find_elements_by_xpath(selector) while not atc_buttons: if self.driver.current_url == models[model][0] + associate_code: log.debug("Refreshing page for " + model) self.driver.refresh() else: log.debug("Error page detected. Redirecting...") self.driver.get(models[model][0] + associate_code) atc_buttons = self.driver.find_elements_by_xpath(selector) sleep(delay) # send notification self.notification_handler.send_notification( f"EVGA BOT: " + model_name + " in stock! Attempting to place order...") # Add to cart atc_buttons[0].click() # Go to checkout selenium_utils.wait_for_page(self.driver, "EVGA - Checkout") selenium_utils.button_click_using_xpath( self.driver, '//*[@id="LFrame_CheckoutButton"]') # Shipping Address screen selenium_utils.wait_for_page(self.driver, "Shopping") log.info("Skip that page.") self.driver.get("https://secure.evga.com/Cart/Checkout_Payment.aspx") selenium_utils.wait_for_page(self.driver, "EVGA - Checkout - Billing Options") log.info("Ensure that we are paying with credit card") sleep(1) # Fix this. WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable( (By.XPATH, './/input[@value="rdoCreditCard"]'))).click() WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable( (By.XPATH, '//*[@id="ctl00_LFrame_btncontinue"]'))).click() selenium_utils.wait_for_element(self.driver, "ctl00_LFrame_txtNameOnCard") log.info("Populate credit card fields") selenium_utils.field_send_keys(self.driver, "ctl00$LFrame$txtNameOnCard", self.credit_card["name"]) selenium_utils.field_send_keys(self.driver, "ctl00$LFrame$txtCardNumber", self.credit_card["number"]) selenium_utils.field_send_keys(self.driver, "ctl00$LFrame$txtCvv", self.credit_card["cvv"]) Select(self.driver.find_element_by_id( "ctl00_LFrame_ddlMonth")).select_by_value( self.credit_card["expiration_month"]) Select(self.driver.find_element_by_id( "ctl00_LFrame_ddlYear")).select_by_value( self.credit_card["expiration_year"]) WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable(( By.XPATH, "/html/body/form/div[3]/div[3]/div/div[1]/div[5]/div[3]/div/div[1]/div/div[@id='checkoutButtons']/input[2]", ))).click() log.info("Finalize Order Page") selenium_utils.wait_for_page(self.driver, "EVGA - Checkout - Finalize Order") WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable( (By.ID, "ctl00_LFrame_cbAgree"))).click() selenium_utils.wait_for_element(self.driver, "ctl00_LFrame_btncontinue") if not test: WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable( (By.ID, "ctl00_LFrame_btncontinue"))).click() log.info("Finalized Order!")
class NvidiaBuyer: def __init__(self, gpu, locale="en_us", test=False, interval=5): self.product_ids = set([]) self.cli_locale = locale.lower() self.locale = self.map_locales() self.session = requests.Session() self.gpu = gpu self.enabled = True self.auto_buy_enabled = False self.attempt = 0 self.started_at = datetime.now() self.test = test self.interval = interval self.gpu_long_name = GPU_DISPLAY_NAMES[gpu] # Disable auto_buy_enabled if the user does not provide a bool. if type(self.auto_buy_enabled) != bool: self.auto_buy_enabled = False adapter = TimeoutHTTPAdapter(max_retries=Retry( total=10, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504], method_whitelist=["HEAD", "GET", "OPTIONS"], )) self.session.mount("https://", adapter) self.session.mount("http://", adapter) self.notification_handler = NotificationHandler() self.get_product_ids() def map_locales(self): if self.cli_locale == "de_at": return "de_de" if self.cli_locale == "fr_be": return "fr_fr" if self.cli_locale == "da_dk": return "en_gb" if self.cli_locale == "cs_cz": return "en_gb" return self.cli_locale def get_product_ids(self): if isinstance(PRODUCT_IDS[self.locale][self.gpu], list): self.product_ids = PRODUCT_IDS[self.locale][self.gpu] if isinstance(PRODUCT_IDS[self.locale][self.gpu], str): self.product_ids = [PRODUCT_IDS[self.locale][self.gpu]] def run_items(self): log.info( f"We have {len(self.product_ids)} product IDs for {self.gpu_long_name}" ) log.info(f"Product IDs: {self.product_ids}") try: with ThreadPoolExecutor( max_workers=len(self.product_ids)) as executor: product_futures = [ executor.submit(self.buy, product_id) for product_id in self.product_ids ] concurrent.futures.wait(product_futures) for fut in product_futures: log.debug(f"Future Result: {fut.result()}") except ProductIDChangedException as ex: log.warning("Product IDs changed.") self.product_ids = set([]) self.get_product_ids() self.run_items() def buy(self, product_id): pass try: log.info( f"Stock Check {product_id} at {self.interval} second intervals." ) while not self.is_in_stock(product_id): self.attempt = self.attempt + 1 time_delta = str(datetime.now() - self.started_at).split(".")[0] with Spinner.get( f"Stock Check ({self.attempt}, have been running for {time_delta})..." ) as s: sleep(self.interval) if self.enabled: cart_success, cart_url = self.get_cart_url(product_id) if cart_success: log.info(f"{self.gpu_long_name} added to cart.") self.enabled = False webbrowser.open(cart_url) self.notification_handler.send_notification( f" {self.gpu_long_name} with product ID: {product_id} in " f"stock: {cart_url}") else: self.buy(product_id) except Timeout: log.error("Had a timeout error.") self.buy(product_id) def is_in_stock(self, product_id): response = self.session.get( NVIDIA_STOCK_API.format(product_id=product_id, locale=self.locale), headers=DEFAULT_HEADERS, ) log.debug(f"Stock check response code: {response.status_code}") if response.status_code != 200: log.debug(response.text) if "PRODUCT_INVENTORY_IN_STOCK" in response.text: return True else: return False def get_cart_url(self, product_id): success, token = self.get_session_token() if not success: return False, "" data = {"products": [{"productId": product_id, "quantity": 1}]} headers = DEFAULT_HEADERS.copy() headers["locale"] = self.locale headers["nvidia_shop_id"] = token headers["Content-Type"] = "application/json" response = self.session.post(url=NVIDIA_ADD_TO_CART_API, headers=headers, data=json.dumps(data)) if response.status_code == 203: response_json = response.json() if "location" in response_json: return True, response_json["location"] else: log.error(response.text) log.error( f"Add to cart failed with {response.status_code}. This is likely an error with nvidia's API." ) return False, "" def get_session_token(self): params = {"format": "json", "locale": self.locale} headers = DEFAULT_HEADERS.copy() headers["locale"] = self.locale response = self.session.get(NVIDIA_TOKEN_URL, headers=DEFAULT_HEADERS, params=params) if response.status_code == 200: response_json = response.json() if "session_token" not in response_json: log.error("Error getting session token.") return False, "" return True, response_json["session_token"] else: log.debug(f"Get Session Token: {response.status_code}")
class NvidiaBuyer: def __init__(self, gpu, locale="en_us", test=False): self.product_ids = set([]) self.cli_locale = locale.lower() self.locale = self.map_locales() self.session = requests.Session() self.gpu = gpu self.enabled = True self.auto_buy_enabled = False self.attempt = 0 self.started_at = datetime.now() self.test = test self.gpu_long_name = GPU_DISPLAY_NAMES[gpu] if path.exists(AUTOBUY_CONFIG_PATH): with open(AUTOBUY_CONFIG_PATH) as json_file: try: self.config = json.load(json_file) except Exception as e: log.error( "Your `autobuy_config.json` file is not valid json.") raise e if self.has_valid_creds(): self.nvidia_login = self.config["NVIDIA_LOGIN"] self.nvidia_password = self.config["NVIDIA_PASSWORD"] self.auto_buy_enabled = self.config["FULL_AUTOBUY"] self.cvv = self.config.get("CVV") self.interval = int(self.config.get("INTERVAL", 5)) else: raise InvalidAutoBuyConfigException(self.config) else: log.info("No Autobuy creds found.") # Disable auto_buy_enabled if the user does not provide a bool. if type(self.auto_buy_enabled) != bool: self.auto_buy_enabled = False adapter = TimeoutHTTPAdapter(max_retries=Retry( total=10, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504], method_whitelist=["HEAD", "GET", "OPTIONS"], )) self.session.mount("https://", adapter) self.session.mount("http://", adapter) self.notification_handler = NotificationHandler() log.info("Opening Webdriver") chrome_options = webdriver.ChromeOptions() chrome_options.add_experimental_option("excludeSwitches", ["enable-logging"]) self.driver = webdriver.Chrome(executable_path=binary_path, options=options, chrome_options=chrome_options) self.sign_in() selenium_utils.add_cookies_to_session_from_driver( self.driver, self.session) log.info("Adding driver cookies to session") log.info("Getting product IDs") self.token_data = self.get_nvidia_access_token() self.payment_option = self.get_payment_options() if not self.payment_option.get("id") or not self.cvv: log.error( "No payment option on account or missing CVV. Disable Autobuy") self.auto_buy_enabled = False else: log.debug(self.payment_option) self.ext_ip = self.get_ext_ip() if not self.auto_buy_enabled: log.info("Closing webdriver") self.driver.close() self.get_product_ids() while len(self.product_ids) == 0: log.info( f"We have no product IDs for {self.gpu_long_name}, retrying until we get a product ID" ) self.get_product_ids() sleep(5) @property def access_token(self): if datetime.today().timestamp() >= self.token_data.get("expires_at"): log.debug("Access token expired") self.token_data = self.get_nvidia_access_token() return self.token_data["access_token"] def has_valid_creds(self): if all(item in self.config.keys() for item in AUTOBUY_CONFIG_KEYS): return True else: return False def map_locales(self): if self.cli_locale == "de_at": return "de_de" if self.cli_locale == "fr_be": return "fr_fr" if self.cli_locale == "da_dk": return "en_gb" if self.cli_locale == "cs_cz": return "en_gb" return self.cli_locale def get_product_ids(self, url=DIGITAL_RIVER_PRODUCT_LIST_URL): log.debug(f"Calling {url}") payload = { "apiKey": DIGITAL_RIVER_API_KEY, "expand": "product", "fields": "product.id,product.displayName,product.pricing", "locale": self.locale, "format": "json", } headers = DEFAULT_HEADERS.copy() headers["locale"] = self.locale response = self.session.get(url, headers=headers, params=payload) log.debug(response.status_code) response_json = response.json() for product_obj in response_json["products"]["product"]: if product_obj["displayName"] == self.gpu_long_name: if self.check_if_locale_corresponds(product_obj["id"]): self.product_ids.add(product_obj["id"]) if response_json["products"].get("nextPage"): self.get_product_ids( url=response_json["products"]["nextPage"]["uri"]) def run_items(self): log.info( f"We have {len(self.product_ids)} product IDs for {self.gpu_long_name}" ) log.info(f"Product IDs: {self.product_ids}") try: with ThreadPoolExecutor( max_workers=len(self.product_ids)) as executor: product_futures = [ executor.submit(self.buy, product_id) for product_id in self.product_ids ] concurrent.futures.wait(product_futures) for fut in product_futures: log.info(fut.result()) except ProductIDChangedException as ex: log.warning("Product IDs changed.") self.product_ids = set([]) self.get_product_ids() self.run_items() def buy(self, product_id): try: log.info( f"Checking stock for {product_id} at {self.interval} second intervals." ) while not self.add_to_cart(product_id) and self.enabled: self.attempt = self.attempt + 1 time_delta = str(datetime.now() - self.started_at).split(".")[0] with Spinner.get( f"Still working (attempt {self.attempt}, have been running for {time_delta})..." ) as s: sleep(self.interval) if self.enabled: self.apply_shopper_details() if self.auto_buy_enabled: self.notification_handler.send_notification( f" {self.gpu_long_name} with product ID: {product_id} available!" ) log.info("Auto buy enabled.") # self.submit_cart() self.selenium_checkout() else: log.info("Auto buy disabled.") cart_url = self.open_cart_url() self.notification_handler.send_notification( f" {self.gpu_long_name} with product ID: {product_id} in stock: {cart_url}" ) self.enabled = False except Timeout: log.error("Had a timeout error.") self.buy(product_id) def open_cart_url(self): log.info("Opening cart.") params = {"token": self.access_token} url = furl(DIGITAL_RIVER_CHECKOUT_URL).set(params) webbrowser.open_new_tab(url.url) return url.url def selenium_checkout(self): log.info("Checking out.") autobuy_btns = autobuy_locale_btns[self.locale] params = {"token": self.access_token} url = furl(DIGITAL_RIVER_CHECKOUT_URL).set(params) self.driver.get(url.url) log.debug( f"Waiting for page title: {PAGE_TITLES_BY_LOCALE[self.locale]['checkout']}" ) selenium_utils.wait_for_page( self.driver, PAGE_TITLES_BY_LOCALE[self.locale]["checkout"]) log.info("Next.") log.debug(f"Clicking on button: {autobuy_btns[0]}") self.driver.find_element_by_xpath( f'//*[@value="{autobuy_btns[0]}"]').click() log.debug(f"Entering security code to 'cardSecurityCode'") security_code = selenium_utils.wait_for_element( self.driver, "cardSecurityCode") security_code.send_keys(self.cvv) log.info("Next.") log.debug(f"Clicking on button: {autobuy_btns[0]}") self.driver.find_element_by_xpath( f'//*[@value="{autobuy_btns[0]}"]').click() try: log.debug( f"Waiting for page title: {PAGE_TITLES_BY_LOCALE[self.locale]['verify_order']}" ) selenium_utils.wait_for_page( self.driver, PAGE_TITLES_BY_LOCALE[self.locale]["verify_order"], 5) except TimeoutException: log.debug("Address validation required?") self.address_validation_page() log.debug( f"Waiting for page title: {PAGE_TITLES_BY_LOCALE[self.locale]['verify_order']}" ) selenium_utils.wait_for_page( self.driver, PAGE_TITLES_BY_LOCALE[self.locale]["verify_order"], 5) if not self.test: log.info("F this captcha lmao. Submitting cart.") self.submit_cart() else: log.info("Test complete. No actual purchase was made.") # log.info("Submit.") # log.debug("Reached order validation page.") # self.driver.save_screenshot("nvidia-order-validation.png") # self.driver.find_element_by_xpath(f'//*[@value="{autobuy_btns[1]}"]').click() # selenium_utils.wait_for_page( # self.driver, PAGE_TITLES_BY_LOCALE[self.locale]["order_completed"], 5 # ) # self.driver.save_screenshot("nvidia-order-finshed.png") # log.info("Done.") def address_validation_page(self): try: selenium_utils.wait_for_page( self.driver, PAGE_TITLES_BY_LOCALE[self.locale]["address_validation"], 5, ) log.debug("Setting suggested shipping information.") selenium_utils.wait_for_element( self.driver, "billingAddressOptionRow2").click() selenium_utils.button_click_using_xpath( self.driver, "//input[@id='selectionButton']") except TimeoutException: log.error("Address validation not required?") def add_to_cart(self, product_id): try: log.debug(f"Checking if item ({product_id}) in stock") params = { "apiKey": DIGITAL_RIVER_API_KEY, "token": self.access_token, "productId": product_id, "format": "json", } response = self.session.post( DIGITAL_RIVER_ADD_TO_CART_API_URL, headers=DEFAULT_HEADERS, params=params, ) if response.status_code == 200: log.info(f"{self.gpu_long_name} ({product_id}) in stock!") return True elif response.status_code == 409: try: response_json = response.json() log.debug(f"Error: {response_json['errors']['error']}") for error in response_json["errors"]["error"]: if error["code"] == "invalid-product-id": raise ProductIDChangedException() except json.decoder.JSONDecodeError as er: log.warning(f"Failed to decode json: {response.text}") else: log.debug("item not in stock") return False except Exception as ex: log.debug(str(ex)) log.debug("The connection has been reset.") return False def get_ext_ip(self): response = self.session.get("https://api.ipify.org?format=json") if response.status_code == 200: return response.json()["ip"] def get_payment_options(self): params = { "apiKey": DIGITAL_RIVER_API_KEY, "token": self.access_token, "format": "json", "expand": "all", } response = self.session.get( DIGITAL_RIVER_PAYMENT_METHODS_API_URL, headers=DEFAULT_HEADERS, params=params, ) log.debug(response.status_code) log.debug(response.json()) if response.status_code == 200: response_json = response.json() try: return response_json["paymentOptions"]["paymentOption"][0] except: return {} def apply_shopper_details(self): log.info("Apply shopper details") params = { "apiKey": DIGITAL_RIVER_API_KEY, "token": self.access_token, "billingAddressId": "", "paymentOptionId": self.payment_option.get("id", ""), "shippingAddressId": "", "expand": "all", } response = self.session.post( DIGITAL_RIVER_APPLY_SHOPPER_DETAILS_API_URL, headers=DEFAULT_HEADERS, params=params, ) log.debug(f"Apply shopper details response: {response.status_code}") if response.status_code == 200: log.info("Success apply_shopper_details") else: log.info("Error applying shopper details") log.debug(json.dumps(response.json(), indent=1)) def submit_cart(self): params = { "apiKey": DIGITAL_RIVER_API_KEY, "token": self.access_token, "format": "json", "expand": "all", } body = { "cart": { "ipAddress": self.ext_ip, "termsOfSalesAcceptance": "true" } } response = self.session.post( DIGITAL_RIVER_SUBMIT_CART_API_URL, headers=DEFAULT_HEADERS, params=params, json=body, ) log.debug(response.status_code) log.debug(response.json()) if response.status_code == 200: log.info("Success submit_cart") def check_if_locale_corresponds(self, product_id): special_locales = [ "en_gb", "de_at", "de_de", "fr_fr", "fr_be", "da_dk", "cs_cz", ] if self.cli_locale in special_locales: url = f"{DIGITAL_RIVER_PRODUCT_LIST_URL}/{product_id}" log.debug(f"Calling {url}") payload = { "apiKey": DIGITAL_RIVER_API_KEY, "expand": "product", "locale": self.locale, "format": "json", } response = self.session.get(url, headers=DEFAULT_HEADERS, params=payload) log.debug(response.status_code) response_json = response.json() return self.cli_locale[3:].upper( ) in response_json["product"]["name"] return True def get_nvidia_access_token(self): log.debug("Getting session token") now = datetime.today() payload = { "apiKey": DIGITAL_RIVER_API_KEY, "format": "json", "locale": self.locale, "currency": "USD", "_": now, } response = self.session.get(NVIDIA_TOKEN_URL, headers=DEFAULT_HEADERS, params=payload) log.debug(response.status_code) data = response.json() log.debug(f"Nvidia access token: {data['access_token']}") data["expires_at"] = round(now.timestamp() + data["expires_in"]) - 60 return data def is_signed_in(self): try: self.driver.find_element_by_id("dr_logout") log.info("Already signed in.") return True except NoSuchElementException: return False def sign_in(self): log.info("Signing in.") self.driver.get( f"https://store.nvidia.com/DRHM/store?Action=Logout&SiteID=nvidia&Locale={self.locale}&ThemeID=326200&Env=BASE&nextAction=help" ) selenium_utils.wait_for_page( self.driver, PAGE_TITLES_BY_LOCALE[self.locale]["signed_in_help"]) if not self.is_signed_in(): email = selenium_utils.wait_for_element(self.driver, "loginEmail") pwd = selenium_utils.wait_for_element(self.driver, "loginPassword") try: email.send_keys(self.nvidia_login) pwd.send_keys(self.nvidia_password) except AttributeError as e: log.error("Missing 'nvidia_login' or 'nvidia_password'") raise e try: action = ActionChains(self.driver) button = self.driver.find_element_by_xpath( '//*[@id="dr_siteButtons"]/input') action.move_to_element(button).click().perform() WebDriverWait(self.driver, 5).until(ec.staleness_of(button)) except NoSuchElementException: log.error("Error signing in.")
class NvidiaBuyer: def __init__(self, gpu, locale="en_us", test=False, interval=5): self.product_ids = set([]) self.cli_locale = locale.lower() self.locale = self.map_locales() self.session = requests.Session() self.gpu = gpu self.enabled = True self.auto_buy_enabled = False self.attempt = 0 self.started_at = datetime.now() self.test = test self.interval = interval self.gpu_long_name = GPU_DISPLAY_NAMES[gpu] # Disable auto_buy_enabled if the user does not provide a bool. if type(self.auto_buy_enabled) != bool: self.auto_buy_enabled = False adapter = TimeoutHTTPAdapter(max_retries=Retry( total=10, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504], method_whitelist=["HEAD", "GET", "OPTIONS"], )) self.session.mount("https://", adapter) self.session.mount("http://", adapter) self.notification_handler = NotificationHandler() self.get_product_ids() print(self.product_ids) def map_locales(self): if self.cli_locale == "de_at": return "de_de" if self.cli_locale == "fr_be": return "fr_fr" if self.cli_locale == "da_dk": return "en_gb" if self.cli_locale == "cs_cz": return "en_gb" return self.cli_locale def get_product_ids(self): self.product_ids = [PRODUCT_IDS[self.locale][self.gpu]] def run_items(self): log.info( f"We have {len(self.product_ids)} product IDs for {self.gpu_long_name}" ) log.info(f"Product IDs: {self.product_ids}") try: with ThreadPoolExecutor( max_workers=len(self.product_ids)) as executor: product_futures = [ executor.submit(self.buy, product_id) for product_id in self.product_ids ] concurrent.futures.wait(product_futures) for fut in product_futures: log.debug(f"Future Result: {fut.result()}") except ProductIDChangedException as ex: log.warning("Product IDs changed.") self.product_ids = set([]) self.get_product_ids() self.run_items() def buy(self, product_id): pass try: log.info( f"Checking stock for {product_id} at {self.interval} second intervals." ) while not self.is_in_stock(product_id) and self.enabled: self.attempt = self.attempt + 1 time_delta = str(datetime.now() - self.started_at).split(".")[0] with Spinner.get( f"Still working (attempt {self.attempt}, have been running for {time_delta})..." ) as s: sleep(self.interval) if self.enabled: log.info(f"{self.gpu_long_name} is in stock. Go buy it.") cart_url = self.open_cart_url(product_id) self.notification_handler.send_notification( f" {self.gpu_long_name} with product ID: {product_id} in " f"stock: {cart_url}") self.enabled = False except Timeout: log.error("Had a timeout error.") self.buy(product_id) def is_in_stock(self, product_id): response = self.session.get( NVIDIA_STOCK_API.format(product_id=product_id, locale=self.locale), headers=DEFAULT_HEADERS, ) log.debug(f"Stock check response code: {response.status_code}") if response.status_code != 200: log.debug(response.text) if "PRODUCT_INVENTORY_IN_STOCK" in response.text: return True else: return False def open_cart_url(self, product_id): log.info("Opening cart.") params = { "Action": "AddItemToRequisition", "SiteID": "nvidia", "Locale": self.locale, "productID": product_id, "quantity": 1, } url = furl(NVIDIA_CART_URL).set(params) webbrowser.open_new_tab(url.url) return url.url
class Evga: def __init__(self, headless=False): if headless: enable_headless() self.driver = webdriver.Chrome(executable_path=binary_path, options=options) self.credit_card = {} self.card_pn = "" self.card_series = "" self.notification_handler = NotificationHandler() try: if path.exists(CONFIG_PATH): with open(CONFIG_PATH) as json_file: config = json.load(json_file) username = config["username"] password = config["password"] self.card_pn = config.get("card_pn") self.card_series = config["card_series"] self.credit_card["name"] = config["credit_card"]["name"] self.credit_card["number"] = config["credit_card"][ "number"] self.credit_card["cvv"] = config["credit_card"]["cvv"] self.credit_card["expiration_month"] = config[ "credit_card"]["expiration_month"] self.credit_card["expiration_year"] = config[ "credit_card"]["expiration_year"] except Exception as e: log.error( f"This is most likely an error with your {CONFIG_PATH} file.") raise e self.login(username, password) def login(self, username, password): """ We're just going to attempt to load cookies, else enter the user info and let the user handle the captcha :param username: :param password: :return: """ self.driver.execute_cdp_cmd( "Network.setUserAgentOverride", { "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.53 Safari/537.36" }, ) self.driver.execute_script( "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})" ) if path.isfile("evga-cookies.pkl"): # check for cookies file self.driver.get("https://www.evga.com") selenium_utils.wait_for_page( self.driver, "EVGA - Intelligent Innovation - Official Website", 300) cookies = pickle.load(open("evga-cookies.pkl", "rb")) for cookie in cookies: self.driver.add_cookie(cookie) self.driver.get("https://www.evga.com") selenium_utils.wait_for_page( self.driver, "EVGA - Intelligent Innovation - Official Website", 300) if (len(self.driver.find_elements_by_id("svg-login")) > 0): # cookies did not provide logged in state self.driver.get(LOGIN_URL) selenium_utils.wait_for_page(self.driver, "EVGA - Intelligent Innovation") selenium_utils.field_send_keys(self.driver, "evga_login", username) selenium_utils.field_send_keys(self.driver, "password", password) log.info("Go do the captcha and log in") selenium_utils.wait_for_page( self.driver, "EVGA - Intelligent Innovation - Official Website", 300) pickle.dump(self.driver.get_cookies(), open("evga-cookies.pkl", "wb")) # save cookies log.info("Logged in!") def buy(self, delay=5, test=False): if test: log.info("Refreshing Page Until Title Matches ...") selenium_utils.wait_for_title( self.driver, "EVGA - Products - Graphics - GeForce 16 Series Family - GTX 1660", "https://www.evga.com/products/ProductList.aspx?type=0&family=GeForce+16+Series+Family&chipset=GTX+1660", ) else: log.info("Refreshing Page Until Title Matches ...") selenium_utils.wait_for_title( self.driver, "EVGA - Products - Graphics - GeForce 30 Series Family - RTX " + self.card_series, "https://www.evga.com/products/productlist.aspx?type=0&family=GeForce+30+Series+Family&chipset=RTX+" + self.card_series, ) log.info("matched chipset=RTX+" + self.card_series + "!") if self.card_pn and not test: # check for card log.info("On GPU list Page") card_btn = self.driver.find_elements_by_xpath( "//a[@href='/products/product.aspx?pn=" + self.card_pn + "']") while not card_btn: log.debug("Refreshing page for GPU") self.driver.refresh() card_btn = self.driver.find_elements_by_xpath( "//a[@href='/products/product.aspx?pn=" + self.card_pn + "']") sleep(delay) card_btn[0].click() # Check for stock log.info("On GPU Page") atc_buttons = self.driver.find_elements_by_xpath( '//input[@class="btnBigAddCart"]') while not atc_buttons: log.debug("Refreshing page for GPU") self.driver.refresh() atc_buttons = self.driver.find_elements_by_xpath( '//input[@class="btnBigAddCart"]') sleep(delay) # Add to cart atc_buttons[0].click() # Send notification that product is available self.notification_handler.send_notification( f"📦 Card found in stock at EVGA (P/N {self.card_pn})…") # Go to checkout selenium_utils.wait_for_page(self.driver, "EVGA - Checkout") selenium_utils.button_click_using_xpath( self.driver, '//*[@id="LFrame_CheckoutButton"]') # Shipping Address screen selenium_utils.wait_for_page(self.driver, "Shopping") log.info("Skip that page.") self.driver.get("https://secure.evga.com/Cart/Checkout_Payment.aspx") selenium_utils.wait_for_page(self.driver, "EVGA - Checkout - Billing Options") log.info("Ensure that we are paying with credit card") sleep(3) WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable( (By.XPATH, './/input[@value="rdoCreditCard"]'))).click() WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable( (By.XPATH, '//*[@id="ctl00_LFrame_btncontinue"]'))).click() selenium_utils.wait_for_element(self.driver, "ctl00_LFrame_txtNameOnCard") log.info("Populate credit card fields") selenium_utils.field_send_keys(self.driver, "ctl00$LFrame$txtNameOnCard", self.credit_card["name"]) selenium_utils.field_send_keys(self.driver, "ctl00$LFrame$txtCardNumber", self.credit_card["number"]) selenium_utils.field_send_keys(self.driver, "ctl00$LFrame$txtCvv", self.credit_card["cvv"]) Select(self.driver.find_element_by_id( "ctl00_LFrame_ddlMonth")).select_by_value( self.credit_card["expiration_month"]) Select(self.driver.find_element_by_id( "ctl00_LFrame_ddlYear")).select_by_value( self.credit_card["expiration_year"]) WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable(( By.XPATH, "/html/body/form/div[3]/div[3]/div/div[1]/div[5]/div[3]/div/div[1]/div/div[@id='checkoutButtons']/input[2]", ))).click() try: WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable(( By.XPATH, "/html/body/form/div[3]/div[3]/div/div[1]/div[5]/div[3]/div/div[1]/div/div[@id='checkoutButtons']/input[2]", ))).click() except: pass log.info("Finalize Order Page") selenium_utils.wait_for_page(self.driver, "EVGA - Checkout - Finalize Order") WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable( (By.ID, "ctl00_LFrame_cbAgree"))).click() if not test: WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable( (By.ID, "ctl00_LFrame_btncontinue"))).click() log.info("Finalized Order!") # Send extra notification alerting user that we've successfully ordered. self.notification_handler.send_notification( f"🎉 Order submitted at EVGA for {self.card_pn}", audio_file="purchase.mp3", )
class Amazon: def __init__(self, username, password, debug=False): self.notification_handler = NotificationHandler() if not debug: chrome_options.add_argument("--headless") self.driver = webdriver.Chrome(executable_path=binary_path, options=options, chrome_options=chrome_options) self.wait = WebDriverWait(self.driver, 10) self.username = username self.password = password self.login() time.sleep(3) def login(self): self.driver.get(LOGIN_URL) self.driver.find_element_by_xpath('//*[@id="ap_email"]').send_keys( self.username + Keys.RETURN) self.driver.find_element_by_xpath('//*[@id="ap_password"]').send_keys( self.password + Keys.RETURN) log.info(f"Logged in as {self.username}") def run_item(self, item_url, price_limit=1000, delay=3): log.info(f"Loading page: {item_url}") self.driver.get(item_url) try: product_title = self.wait.until( presence_of_element_located((By.ID, "productTitle"))) log.info(f"Loaded page for {product_title.text}") except: log.error(self.driver.current_url) availability = self.driver.find_element_by_xpath( '//*[@id="availability"]').text.replace("\n", " ") log.info(f"Initial availability message is: {availability}") while not self.driver.find_elements_by_xpath( '//*[@id="buy-now-button"]'): try: self.driver.refresh() log.info("Refreshing page.") availability = self.wait.until( presence_of_element_located( (By.ID, "availability"))).text.replace("\n", " ") log.info(f"Current availability message is: {availability}") time.sleep(delay) except TimeoutException as _: log.warn("A polling request timed out. Retrying.") log.info("Item in stock, buy now button found!") price_str = self.driver.find_element_by_id("priceblock_ourprice").text price_int = int(round(float(price_str.strip("$")))) if price_int < price_limit: log.info(f"Attempting to buy item for {price_int}") self.buy_now() else: self.notification_handler.send_notification( f"Item was found, but price is at {price_int} so we did not buy it." ) log.info(f"Price was too high {price_int}") def buy_now(self): self.driver.find_element_by_xpath('//*[@id="buy-now-button"]').click() log.info("Clicking 'Buy Now'.") try: place_order = WebDriverWait(self.driver, 2).until( presence_of_element_located( (By.ID, "turbo-checkout-pyo-button"))) except: log.debug("Went to check out page.") place_order = WebDriverWait(self.driver, 2).until( presence_of_element_located((By.NAME, "placeYourOrder1"))) log.info("Clicking 'Place Your Order'.") place_order.click() self.notification_handler.send_notification( f"Item was purchased! Check your Amazon account.") def force_stop(self): self.driver.stop_client()
def __init__(self, sku_id): self.notification_handler = NotificationHandler() self.sku_id = sku_id self.session = requests.Session() self.auto_buy = False self.account = {"username": "", "password": ""} adapter = HTTPAdapter(max_retries=Retry( total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504], method_whitelist=["HEAD", "GET", "OPTIONS", "POST"], )) self.session.mount("https://", adapter) self.session.mount("http://", adapter) response = self.session.get(BEST_BUY_PDP_URL.format(sku=self.sku_id), headers=DEFAULT_HEADERS) log.info(f"PDP Request: {response.status_code}") self.product_url = response.url log.info(f"Product URL: {self.product_url}") self.session.get(self.product_url) log.info(f"Product URL Request: {response.status_code}") if self.auto_buy: log.info("Loading headless driver.") # options.add_argument('headless') # This messes up the cookies for some reason. options.add_argument( "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" ) chrome_options.add_argument( "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" ) self.driver = webdriver.Chrome( executable_path=binary_path, options=options, chrome_options=chrome_options, ) log.info("Loading https://www.bestbuy.com.") self.login() self.driver.get(self.product_url) cookies = self.driver.get_cookies() [ self.session.cookies.set_cookie( requests.cookies.create_cookie( domain=cookie["domain"], name=cookie["name"], value=cookie["value"], )) for cookie in cookies ] self.driver.quit() log.info("Calling location/v1/US/approximate") log.info( self.session.get( "https://www.bestbuy.com/location/v1/US/approximate", headers=DEFAULT_HEADERS, ).status_code) log.info("Calling basket/v1/basketCount") log.info( self.session.get( "https://www.bestbuy.com/basket/v1/basketCount", headers={ "x-client-id": "browse", "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", "Accept": "application/json", }, ).status_code)
class Amazon: def __init__(self, username, password, item_url, headless=False): self.notification_handler = NotificationHandler() if headless: enable_headless() h = hashlib.md5(item_url.encode()).hexdigest() options.add_argument(f"user-data-dir=.profile-amz-{h}") self.driver = webdriver.Chrome(executable_path=binary_path, options=options) self.wait = WebDriverWait(self.driver, 10) self.username = username self.password = password self.driver.get(BASE_URL) if self.is_logged_in(): log.info("Already logged in") else: self.login() time.sleep(15) def is_logged_in(self): try: text = wait_for_element(self.driver, "nav-link-accountList").text return "Hello, Sign in" not in text except Exception: return False def login(self): self.driver.get(LOGIN_URL) self.driver.find_element_by_xpath('//*[@id="ap_email"]').send_keys( self.username + Keys.RETURN) self.driver.find_element_by_xpath('//*[@id="ap_password"]').send_keys( self.password + Keys.RETURN) log.info(f"Logged in as {self.username}") def run_item(self, item_url, price_limit=1000, delay=3): log.info(f"Loading page: {item_url}") self.driver.get(item_url) item = "" try: product_title = self.wait.until( presence_of_element_located((By.ID, "productTitle"))) log.info(f"Loaded page for {product_title.text}") item = product_title.text[:100].strip() except: log.error(self.driver.current_url) availability = self.driver.find_element_by_xpath( '//*[@id="availability"]').text.replace("\n", " ") log.info(f"Initial availability message is: {availability}") while not self.driver.find_elements_by_xpath( '//*[@id="buy-now-button"]'): try: self.driver.refresh() log.info(f"Refreshing for {item}...") availability = self.wait.until( presence_of_element_located( (By.ID, "availability"))).text.replace("\n", " ") log.info(f"Current availability message is: {availability}") time.sleep(delay) except TimeoutException as _: log.warn("A polling request timed out. Retrying.") log.info("Item in stock, buy now button found!") try: price_str = self.driver.find_element_by_id( "priceblock_ourprice").text except NoSuchElementException as _: price_str = self.driver.find_element_by_id( "priceblock_dealprice").text price_int = int(round(float(price_str.strip("$")))) if price_int < price_limit: log.info(f"Attempting to buy item for {price_int}") self.buy_now() else: self.notification_handler.send_notification( f"Item was found, but price is at {price_int} so we did not buy it." ) log.info(f"Price was too high {price_int}") def buy_now(self): self.driver.find_element_by_xpath('//*[@id="buy-now-button"]').click() log.info("Clicking 'Buy Now'.") try: place_order = WebDriverWait(self.driver, 2).until( presence_of_element_located( (By.ID, "turbo-checkout-pyo-button"))) except: log.debug("Went to check out page.") place_order = WebDriverWait(self.driver, 2).until( presence_of_element_located((By.NAME, "placeYourOrder1"))) log.info("Clicking 'Place Your Order'.") place_order.click() self.notification_handler.send_notification( f"Item was purchased! Check your Amazon account.") def force_stop(self): self.driver.stop_client()
class NvidiaBuyer: def __init__(self, gpu, locale="en_us", test=False, interval=5): self.product_ids = set([]) self.cli_locale = locale.lower() self.locale = self.map_locales() self.session = requests.Session() self.gpu = gpu self.enabled = True self.auto_buy_enabled = False self.attempt = 0 self.started_at = datetime.now() self.test = test self.interval = interval self.gpu_long_name = GPU_DISPLAY_NAMES[gpu] self.cj = browser_cookie3.load(".nvidia.com") self.session.cookies = self.cj # Disable auto_buy_enabled if the user does not provide a bool. if type(self.auto_buy_enabled) != bool: self.auto_buy_enabled = False adapter = TimeoutHTTPAdapter() self.session.mount("https://", adapter) self.session.mount("http://", adapter) self.notification_handler = NotificationHandler() self.get_product_ids() def map_locales(self): if self.cli_locale == "de_at": return "de_de" if self.cli_locale == "fr_be": return "fr_fr" if self.cli_locale == "da_dk": return "en_gb" if self.cli_locale == "cs_cz": return "en_gb" return self.cli_locale def get_product_ids(self): if isinstance(PRODUCT_IDS[self.cli_locale][self.gpu], list): self.product_ids = PRODUCT_IDS[self.cli_locale][self.gpu] if isinstance(PRODUCT_IDS[self.cli_locale][self.gpu], str): self.product_ids = [PRODUCT_IDS[self.cli_locale][self.gpu]] def run_items(self): log.info( f"We have {len(self.product_ids)} product IDs for {self.gpu_long_name}" ) log.info(f"Product IDs: {self.product_ids}") try: with ThreadPoolExecutor( max_workers=len(self.product_ids)) as executor: product_futures = [ executor.submit(self.buy, product_id) for product_id in self.product_ids ] concurrent.futures.wait(product_futures) for fut in product_futures: log.debug(f"Future Result: {fut.result()}") except ProductIDChangedException as ex: log.warning("Product IDs changed.") self.product_ids = set([]) self.get_product_ids() self.run_items() def buy(self, product_id): try: log.info( f"Stock Check {product_id} at {self.interval} second intervals." ) while not self.is_in_stock(product_id): self.attempt = self.attempt + 1 time_delta = str(datetime.now() - self.started_at).split(".")[0] with Spinner.get( f"Stock Check ({self.attempt}, have been running for {time_delta})..." ) as s: sleep(self.interval) if self.enabled: cart_success = self.add_to_cart(product_id) if cart_success: log.info(f"{self.gpu_long_name} added to cart.") self.enabled = False webbrowser.open(NVIDIA_CART_URL) self.notification_handler.send_notification( f" {self.gpu_long_name} with product ID: {product_id} in " f"stock: {NVIDIA_CART_URL}") else: self.notification_handler.send_notification( f" ERROR: Attempted to add {self.gpu_long_name} to cart but couldn't, check manually!" ) self.buy(product_id) except requests.exceptions.RequestException as e: log.warning( "Connection error while calling Nvidia API. API may be down.") log.info( f"Got an unexpected reply from the server, API may be down, nothing we can do but try again" ) self.buy(product_id) def is_in_stock(self, product_id): try: response = self.session.get( NVIDIA_STOCK_API.format( product_id=product_id, locale=self.locale, currency=CURRENCY_LOCALE_MAP.get(self.locale, "USD"), cookies=self.cj, ), headers=DEFAULT_HEADERS, ) log.debug(f"Stock check response code: {response.status_code}") if response.status_code != 200: log.debug(response.text) if "PRODUCT_INVENTORY_IN_STOCK" in response.text: return True else: return False except requests.exceptions.RequestException as e: log.info( f"Got an unexpected reply from the server, API may be down, nothing we can do but try again" ) return False def add_to_cart(self, product_id): try: success, token = self.get_session_token() if not success: return False log.info(f"Session token: {token}") data = {"products": [{"productId": product_id, "quantity": 1}]} headers = DEFAULT_HEADERS.copy() headers["locale"] = self.locale headers["nvidia_shop_id"] = token headers["Content-Type"] = "application/json" response = self.session.post( url=NVIDIA_ADD_TO_CART_API, headers=headers, data=json.dumps(data), cookies=self.cj, ) if response.status_code == 200: response_json = response.json() print(response_json) if "successfully" in response_json["message"]: return True else: log.error(response.text) log.error( f"Add to cart failed with {response.status_code}. This is likely an error with nvidia's API." ) return False except requests.exceptions.RequestException as e: log.info(e) log.info( f"Got an unexpected reply from the server, API may be down, nothing we can do but try again" ) return False def get_session_token(self): """ Ok now this works, but I dont know when the cookies expire so might be unstable. :return: """ params = {"format": "json", "locale": self.locale} headers = DEFAULT_HEADERS.copy() headers["locale"] = self.locale headers["cookie"] = "; ".join([ f"{cookie.name}={cookie.value}" for cookie in self.session.cookies ]) try: response = self.session.get( NVIDIA_TOKEN_URL, headers=headers, params=params, cookies=self.cj, ) if response.status_code == 200: response_json = response.json() if "session_token" not in response_json: log.error("Error getting session token.") return False, "" return True, response_json["session_token"] else: log.debug(f"Get Session Token: {response.status_code}") except requests.exceptions.RequestException as e: log.info( f"Got an unexpected reply from the server, API may be down, nothing we can do but try again" ) return False
class NvidiaBuyer: def __init__(self, gpu, locale="en_us"): self.product_ids = [] self.cli_locale = locale.lower() self.locale = self.map_locales() self.session = requests.Session() self.gpu = gpu self.enabled = True try: self.gpu_long_name = GPU_DISPLAY_NAMES[gpu] except Exception as e: log.error("Invalid GPU name.") raise e adapter = HTTPAdapter(max_retries=Retry( total=10, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504], method_whitelist=["HEAD", "GET", "OPTIONS"], )) self.session.mount("https://", adapter) self.session.mount("http://", adapter) self.notification_handler = NotificationHandler() self.autobuy_handler = AutoBuy() log.info("Getting product IDs") self.get_product_ids() while len(self.product_ids) == 0: log.info( f"We have no product IDs for {self.gpu_long_name}, retrying until we get a product ID" ) self.get_product_ids() sleep(5) def map_locales(self): if self.cli_locale == "de_at": return "de_de" if self.cli_locale == "fr_be": return "fr_fr" return self.cli_locale def get_product_ids(self, url=DIGITAL_RIVER_PRODUCT_LIST_URL): log.debug(f"Calling {url}") payload = { "apiKey": DIGITAL_RIVER_API_KEY, "expand": "product", "fields": "product.id,product.displayName,product.pricing", "locale": self.locale, } headers = DEFAULT_HEADERS.copy() headers["locale"] = self.locale response = self.session.get(url, headers=headers, params=payload) log.debug(response.status_code) response_json = response.json() for product_obj in response_json["products"]["product"]: if product_obj["displayName"] == self.gpu_long_name: if self.check_if_locale_corresponds(product_obj["id"]): self.product_ids.append(product_obj["id"]) if response_json["products"].get("nextPage"): self.get_product_ids( url=response_json["products"]["nextPage"]["uri"]) def run_items(self): log.info( f"We have {len(self.product_ids)} product IDs for {self.gpu_long_name}" ) log.info(f"Product IDs: {self.product_ids}") with ThreadPoolExecutor(max_workers=len(self.product_ids)) as executor: [ executor.submit(self.buy, product_id) for product_id in self.product_ids ] def buy(self, product_id): log.info( f"Checking stock for {self.gpu_long_name} with product ID: {product_id}..." ) cart_url = self.get_cart_url(product_id) while cart_url is None and self.enabled: log.debug( f"{self.gpu_long_name} with product ID: {product_id} not in stock." ) sleep(5) cart_url = self.get_cart_url(product_id) log.info( f" {self.gpu_long_name} with product ID: {product_id} in stock: {cart_url}" ) self.notification_handler.send_notification( f" {self.gpu_long_name} with product ID: {product_id} in stock: {cart_url}" ) if self.autobuy_handler.enabled: log.info("Starting auto buy.") self.autobuy_handler.auto_buy(cart_url, self.locale) log.info("Auto buy complete.") self.enabled = False else: webbrowser.open_new(cart_url) log.info(f"Opened {cart_url}.") self.enabled = False def get_cart_url(self, product_id): access_token = self.get_nividia_access_token() payload = { "apiKey": DIGITAL_RIVER_API_KEY, "format": "json", "method": "post", "productId": product_id, "locale": self.locale, "quantity": 1, "token": access_token, "_": datetime.now(), } log.debug(f"Adding {self.gpu_long_name} ({product_id}) to cart") response = self.session.get(DIGITAL_RIVER_ADD_TO_CART_URL, headers=DEFAULT_HEADERS, params=payload) log.debug(response.status_code) if response.status_code not in CART_SUCCESS_CODES: return log.debug(self.session.cookies) params = {"token": access_token} url = furl(DIGITAL_RIVER_CHECKOUT_URL).set(params) return url.url def check_if_locale_corresponds(self, product_id): special_locales = ["en_gb", "de_at", "de_de", "fr_fr", "fr_be"] if self.cli_locale in special_locales: url = f"{DIGITAL_RIVER_PRODUCT_LIST_URL}/{product_id}" log.debug(f"Calling {url}") payload = { "apiKey": DIGITAL_RIVER_API_KEY, "expand": "product", "locale": self.locale, "format": "json", } response = self.session.get(url, headers=DEFAULT_HEADERS, params=payload) log.debug(response.status_code) response_json = response.json() return self.cli_locale[3:].upper( ) in response_json["product"]["name"] return True def get_nividia_access_token(self): log.debug("Getting session token") payload = { "apiKey": DIGITAL_RIVER_API_KEY, "format": "json", "locale": self.locale, "currency": "USD", "_": datetime.today(), } response = self.session.get(NVIDIA_TOKEN_URL, headers=DEFAULT_HEADERS, params=payload) log.debug(response.status_code) return response.json()["access_token"]
class BestBuyHandler: def __init__(self, sku_id): self.notification_handler = NotificationHandler() self.sku_id = sku_id self.session = requests.Session() self.auto_buy = False self.account = {"username": "", "password": ""} adapter = HTTPAdapter(max_retries=Retry( total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504], method_whitelist=["HEAD", "GET", "OPTIONS", "POST"], )) self.session.mount("https://", adapter) self.session.mount("http://", adapter) response = self.session.get(BEST_BUY_PDP_URL.format(sku=self.sku_id), headers=DEFAULT_HEADERS) log.info(f"PDP Request: {response.status_code}") self.product_url = response.url log.info(f"Product URL: {self.product_url}") self.session.get(self.product_url) log.info(f"Product URL Request: {response.status_code}") if self.auto_buy: log.info("Loading headless driver.") # options.add_argument('headless') # This messes up the cookies for some reason. options.add_argument( "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" ) chrome_options.add_argument( "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" ) self.driver = webdriver.Chrome( executable_path=binary_path, options=options, chrome_options=chrome_options, ) log.info("Loading https://www.bestbuy.com.") self.login() self.driver.get(self.product_url) cookies = self.driver.get_cookies() [ self.session.cookies.set_cookie( requests.cookies.create_cookie( domain=cookie["domain"], name=cookie["name"], value=cookie["value"], )) for cookie in cookies ] self.driver.quit() log.info("Calling location/v1/US/approximate") log.info( self.session.get( "https://www.bestbuy.com/location/v1/US/approximate", headers=DEFAULT_HEADERS, ).status_code) log.info("Calling basket/v1/basketCount") log.info( self.session.get( "https://www.bestbuy.com/basket/v1/basketCount", headers={ "x-client-id": "browse", "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", "Accept": "application/json", }, ).status_code) def login(self): self.driver.get("https://www.bestbuy.com/identity/global/signin") self.driver.find_element_by_xpath('//*[@id="fld-e"]').send_keys( self.account["username"]) self.driver.find_element_by_xpath('//*[@id="fld-p1"]').send_keys( self.account["password"]) self.driver.find_element_by_xpath( "/html/body/div[1]/div/section/main/div[1]/div/div/div/div/form/div[3]/div/label/div/i" ).click() self.driver.find_element_by_xpath( "/html/body/div[1]/div/section/main/div[1]/div/div/div/div/form/div[4]/button" ).click() WebDriverWait( self.driver, 10).until(lambda x: "Official Online Store" in self.driver.title) def run_item(self): while not self.in_stock(): sleep(5) log.info(f"Item {self.sku_id} is in stock!") if self.auto_buy: self.auto_checkout() else: cart_url = self.add_to_cart() self.notification_handler.send_notification( f"SKU: {self.sku_id} in stock: {cart_url}") def in_stock(self): log.info("Checking stock") url = "https://www.bestbuy.com/api/tcfb/model.json?paths=%5B%5B%22shop%22%2C%22scds%22%2C%22v2%22%2C%22page%22%2C%22tenants%22%2C%22bbypres%22%2C%22pages%22%2C%22globalnavigationv5sv%22%2C%22header%22%5D%2C%5B%22shop%22%2C%22buttonstate%22%2C%22v5%22%2C%22item%22%2C%22skus%22%2C{}%2C%22conditions%22%2C%22NONE%22%2C%22destinationZipCode%22%2C%22%2520%22%2C%22storeId%22%2C%22%2520%22%2C%22context%22%2C%22cyp%22%2C%22addAll%22%2C%22false%22%5D%5D&method=get".format( self.sku_id) response = self.session.get(url, headers=DEFAULT_HEADERS) log.info(f"Stock check response code: {response.status_code}") try: response_json = response.json() item_json = find_values(json.dumps(response_json), "buttonStateResponseInfos") item_state = item_json[0][0]["buttonState"] log.info(f"Item state is: {item_state}") if item_json[0][0][ "skuId"] == self.sku_id and item_state == "ADD_TO_CART": return True else: return False except Exception as e: log.warning( "Error parsing json. Using string search to determine state.") log.info(response_json) log.error(e) if "ADD_TO_CART" in response.text: log.info("Item is in stock!") return True else: log.info("Item is out of stock") return False def add_to_cart(self): webbrowser.open_new(BEST_BUY_CART_URL.format(sku=self.sku_id)) return BEST_BUY_CART_URL.format(sku=self.sku_id) def auto_checkout(self): tas_data = self.get_tas_data() self.auto_add_to_cart() self.start_checkout() self.submit_shipping() self.submit_payment(tas_data) def auto_add_to_cart(self): log.info("Attempting to auto add to cart...") body = {"items": [{"skuId": self.sku_id}]} headers = { "Accept": "application/json", "authority": "www.bestbuy.com", "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", "Content-Type": "application/json; charset=UTF-8", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Dest": "empty", "origin": "https://www.bestbuy.com", "referer": self.product_url, "Content-Length": str(len(json.dumps(body))), } # [ # log.info({'name': c.name, 'value': c.value, 'domain': c.domain, 'path': c.path}) # for c in self.session.cookies # ] log.info("Making request") response = self.session.post(BEST_BUY_ADD_TO_CART_API_URL, json=body, headers=headers, timeout=5) log.info(response.status_code) if (response.status_code == 200 and response.json()["cartCount"] > 0 and self.sku_id in response.text): log.info(f"Added {self.sku_id} to cart!") log.info(response.json()) else: log.info(response.status_code) log.info(response.json()) def start_checkout(self): headers = { "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", "accept-encoding": "gzip, deflate, br", "accept-language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36", } while True: log.info("Starting Checkout") response = self.session.post( "https://www.bestbuy.com/cart/d/checkout", headers=headers, timeout=5) if response.status_code == 200: response_json = response.json() log.info(response_json) self.order_id = response_json["updateData"]["order"]["id"] self.item_id = response_json["updateData"]["order"][ "lineItems"][0]["id"] log.info(f"Started Checkout for order id: {self.order_id}") log.info(response_json) if response_json["updateData"]["redirectUrl"]: self.session.get( response_json["updateData"]["redirectUrl"], headers=headers) return log.info("Error Starting Checkout") sleep(5) def submit_shipping(self): log.info("Starting Checkout") headers = { "accept": "application/json, text/javascript, */*; q=0.01", "accept-encoding": "gzip, deflate, br", "accept-language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7", "content-type": "application/json", "origin": "https://www.bestbuy.com", "referer": "https://www.bestbuy.com/cart", "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36", "x-user-interface": "DotCom-Optimized", "x-order-id": self.order_id, } while True: log.info("Submitting Shipping") body = {"selected": "SHIPPING"} response = self.session.put( "https://www.bestbuy.com/cart/item/{item_id}/fulfillment". format(item_id=self.item_id), headers=headers, json=body, ) response_json = response.json() log.info(response.status_code) log.info(response_json) if (response.status_code == 200 and response_json["order"]["id"] == self.order_id): log.info("Submitted Shipping") return True else: log.info("Error Submitting Shipping") def submit_payment(self, tas_data): body = { "items": [{ "id": self.item_id, "type": "DEFAULT", "selectedFulfillment": { "shipping": { "address": {} } }, "giftMessageSelected": False, }] } headers = { "accept": "application/com.bestbuy.order+json", "accept-encoding": "gzip, deflate, br", "accept-language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7", "content-type": "application/json", "origin": "https://www.bestbuy.com", "referer": "https://www.bestbuy.com/checkout/r/fulfillment", "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36", "x-user-interface": "DotCom-Optimized", } r = self.session.patch( "https://www.bestbuy.com/checkout/d/orders/{}/".format( self.order_id), json=body, headers=headers, ) [ log.info({ "name": c.name, "value": c.value, "domain": c.domain, "path": c.path }) for c in self.session.cookies ] log.info(r.status_code) log.info(r.text) def get_tas_data(self): headers = { "accept": "*/*", "accept-encoding": "gzip, deflate, br", "accept-language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7", "content-type": "application/json", "referer": "https://www.bestbuy.com/checkout/r/payment", "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36", } while True: try: log.info("Getting TAS Data") r = requests.get( "https://www.bestbuy.com/api/csiservice/v2/key/tas", headers=headers) log.info("Got TAS Data") return json.loads(r.text) except Exception as e: sleep(5)
"You should try running pipenv shell and pipenv install per the install instructions" ) print("Or you should only use Python 3.8.X per the instructions.") print("If you are attempting to run multiple bots, this is not supported.") print("You are on your own to figure this out.") exit(0) import time from notifications.notifications import NotificationHandler, TIME_FORMAT from stores.amazon import Amazon from stores.bestbuy import BestBuyHandler from utils import selenium_utils from utils.logger import log from utils.version import check_version notification_handler = NotificationHandler() try: check_version() except Exception as e: log.error(e) def handler(signal, frame): log.info("Caught the stop, exiting.") exit(0) def notify_on_crash(func): @wraps(func) def decorator(*args, **kwargs):