def decorator(*args, **kwargs): try: func(*args, **kwargs) except KeyboardInterrupt: pass except: NotificationHandler.send_notification(f"FairGame has crashed.") raise
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 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 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.")
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): 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() 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 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()
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)
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"]
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: 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.")
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 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 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()