예제 #1
0
파일: cli.py 프로젝트: Sloop0/fairgame
 def decorator(*args, **kwargs):
     try:
         func(*args, **kwargs)
     except KeyboardInterrupt:
         pass
     except:
         NotificationHandler.send_notification(f"FairGame has crashed.")
         raise
예제 #2
0
파일: cli.py 프로젝트: Sloop0/fairgame
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)
예제 #3
0
파일: nvidia.py 프로젝트: trapki/nvidia-bot
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
예제 #4
0
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.")
예제 #5
0
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!")
예제 #6
0
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.")
예제 #7
0
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}")
예제 #8
0
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()
예제 #9
0
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)
예제 #10
0
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"]
예제 #11
0
def testnotification():
    notification_handler = NotificationHandler()
    notification_handler.send_notification(f"Notifications Test")
예제 #12
0
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.")
예제 #13
0
파일: evga.py 프로젝트: zellaj1/nvidia-bot
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",
        )
예제 #14
0
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
예제 #15
0
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()