From 953ee9c479f4b190a3d463926ed2c250f5ea5c6e Mon Sep 17 00:00:00 2001 From: Hari-Nagarajan Date: Sun, 20 Sep 2020 20:15:35 -0400 Subject: [PATCH] feat: Autobuy for bestbuy WIP. Formatting. Signed-off-by: Hari-Nagarajan --- .gitignore | 11 ++- Pipfile | 1 - app.py | 220 ------------------------------------------ cli/cli.py | 14 ++- cli/utils.py | 1 - stores/bestbuy.py | 238 ++++++++++++++++++++++++++++++---------------- 6 files changed, 172 insertions(+), 313 deletions(-) diff --git a/.gitignore b/.gitignore index 917dd31e..e0eeaddc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ -/dist/ -/.idea/ -/build/ -/Pipfile.lock +dist/ +.idea/ +build/ +Pipfile.lock twilio_config.json -/discord_config.json +discord_config.json telegram_config.json +.profile \ No newline at end of file diff --git a/Pipfile b/Pipfile index 50acd331..5bbe7bd0 100644 --- a/Pipfile +++ b/Pipfile @@ -11,7 +11,6 @@ requests = "*" click = "*" selenium = "*" chromedriver-py = "==85.0.4183.87" -guizero = "*" furl = "*" twilio = "*" discord-webhook = "*" diff --git a/app.py b/app.py index faeed2af..f981c6b2 100644 --- a/app.py +++ b/app.py @@ -1,225 +1,5 @@ -import json -from concurrent.futures import ThreadPoolExecutor - -from guizero import App, PushButton, Box, TextBox, Text, Combo - from cli import cli -from stores.amazon import Amazon -from stores.nvidia import GPU_DISPLAY_NAMES -from stores.nvidia import NvidiaBuyer -from utils.logger import log - - -class MainUI: - def __init__(self): - self.app = App(layout="grid", width=550, title="3080 Bot") - self.amzn_input_data = self.load_amzn_options() - - self.amazon_executor = ThreadPoolExecutor(max_workers=3) - self.nvidia_executor = ThreadPoolExecutor(max_workers=3) - - self.amazon_box = Box( - self.app, - grid=[0, 0], - height="fill", - width="550", - layout="grid", - align="left", - ) - self.amazon_status = Text( - self.amazon_box, - grid=[0, 1], - align="left", - bg="black", - color="white", - height="fill", - text="", - ) - self.amazon_status.text_size = 10 - - self.amazon_inputs_box = Box( - self.amazon_box, - grid=[0, 0], - border=1, - height="fill", - width="fill", - layout="grid", - align="left", - ) - - self.start_button = PushButton( - self.amazon_inputs_box, - command=self.start_amzn, - text="start", - grid=[4, 0], - align="right", - ) - self.stop_button = PushButton( - self.amazon_inputs_box, - command=self.stop_amzn, - text="stop", - enabled=False, - grid=[4, 1], - align="right", - ) - - Text( - self.amazon_inputs_box, - text="Amazon Email", - grid=[0, 0], - align="left", - ) - self.amazon_email = TextBox( - self.amazon_inputs_box, - align="left", - grid=[1, 0], - width=20, - text=self.amzn_input_data["amazon_email"], - ) - - Text(self.amazon_inputs_box, text="Amazon Password", grid=[0, 1], align="left") - self.amazon_password = TextBox( - self.amazon_inputs_box, - align="left", - grid=[1, 1], - hide_text=True, - width=20, - text=self.amzn_input_data["amazon_password"], - ) - - Text(self.amazon_inputs_box, text="Item URL", grid=[2, 0], align="left") - self.amazon_item_url = TextBox( - self.amazon_inputs_box, - align="left", - grid=[3, 0], - text=self.amzn_input_data["amazon_item_url"], - width=20, - ) - - Text(self.amazon_inputs_box, text="Price Limit", grid=[2, 1], align="left") - self.amazon_price_limit = TextBox( - self.amazon_inputs_box, - align="left", - grid=[3, 1], - text=self.amzn_input_data["amazon_price_limit"], - width=20, - ) - - self.nvidia_box = Box( - self.app, - grid=[0, 1], - border=1, - height="fill", - width=200, - layout="grid", - align="left", - ) - self.nvidia_inputs_box = Box( - self.nvidia_box, - grid=[0, 0], - border=1, - height="fill", - width=200, - layout="grid", - ) - self.start_button_nv = PushButton( - self.nvidia_inputs_box, command=self.start_nv, text="start", grid=[1, 0] - ) - self.stop_button_nv = PushButton( - self.nvidia_inputs_box, - command=self.stop_nv, - text="stop", - enabled=False, - grid=[2, 0], - ) - self.nv_status = Text( - self.nvidia_box, - grid=[0, 1], - align="left", - bg="black", - color="white", - height="fill", - text="", - ) - self.nv_status.text_size = 10 - - self.nv_gpu = Combo( - self.nvidia_inputs_box, options=list(GPU_DISPLAY_NAMES.keys()), grid=[0, 0] - ) - - def save_amzn_options(self): - data = { - "amazon_email": self.amazon_email.value, - "amazon_password": self.amazon_password.value, - "amazon_item_url": self.amazon_item_url.value, - "amazon_price_limit": self.amazon_price_limit.value, - } - with open("amazon.json", "w") as outfile: - json.dump(data, outfile) - - def load_amzn_options(self): - try: - with open("amazon.json") as json_file: - return json.load(json_file) - except: - return { - "amazon_email": None, - "amazon_password": None, - "amazon_item_url": None, - "amazon_price_limit": None, - } - - def amazon_run_item(self): - amzn_obj = Amazon( - username=self.amazon_email.value, - password=self.amazon_password.value, - debug=True, - ) - amzn_obj.run_item( - item_url=self.amazon_item_url.value, - price_limit=self.amazon_price_limit.value, - ) - - def start_amzn(self): - if ( - self.amazon_email.value - and self.amazon_password.value - and self.amazon_price_limit.value - and self.amazon_item_url.value - ): - log.info("Starting amazon bot.") - self.save_amzn_options() - self.start_button.disable() - self.stop_button.enable() - self.amazon_status.value = "Running." - self.amazon_executor.submit(self.amazon_run_item) - - def stop_amzn(self): - self.amazon_executor.shutdown() - self.amazon_status.value = "Stopped." - self.start_button.enable() - self.stop_button.disable() - - def nv_run(self): - nv = NvidiaBuyer() - nv.buy(self.nv_gpu.value) - - def start_nv(self): - if self.nv_gpu.value: - log.info("Starting NV bot.") - self.nv_status.value = "Running." - self.start_button_nv.disable() - self.stop_button_nv.enable() - self.amazon_executor.submit(self.nv_run) - - def stop_nv(self): - self.nvidia_executor.shutdown() - self.nv_status.value = "Stopped." - self.start_button_nv.enable() - self.stop_button_nv.disable() if __name__ == "__main__": - # main_ui = MainUI() - # main_ui.app.display() cli.main() diff --git a/cli/cli.py b/cli/cli.py index 2d3b89b4..31825546 100644 --- a/cli/cli.py +++ b/cli/cli.py @@ -14,11 +14,17 @@ def main(): @click.command() -@click.option("--gpu", type=click.Choice(GPU_DISPLAY_NAMES, case_sensitive=False), prompt="What GPU are you after?", - cls=QuestionaryOption) @click.option( - "--locale", type=click.Choice(ACCEPTED_LOCALES, case_sensitive=False), prompt="What locale shall we use?", - cls=QuestionaryOption + "--gpu", + type=click.Choice(GPU_DISPLAY_NAMES, case_sensitive=False), + prompt="What GPU are you after?", + cls=QuestionaryOption, +) +@click.option( + "--locale", + type=click.Choice(ACCEPTED_LOCALES, case_sensitive=False), + prompt="What locale shall we use?", + cls=QuestionaryOption, ) def nvidia(gpu, locale): nv = NvidiaBuyer(gpu, locale) diff --git a/cli/utils.py b/cli/utils.py index 78447240..4cbaca48 100644 --- a/cli/utils.py +++ b/cli/utils.py @@ -3,7 +3,6 @@ class QuestionaryOption(click.Option): - def __init__(self, param_decls=None, **attrs): click.Option.__init__(self, param_decls, **attrs) diff --git a/stores/bestbuy.py b/stores/bestbuy.py index 13fccfca..8adbe461 100644 --- a/stores/bestbuy.py +++ b/stores/bestbuy.py @@ -2,13 +2,18 @@ import webbrowser from time import sleep +from chromedriver_py import binary_path # this will get you the path variable +from selenium import webdriver +from selenium.webdriver import ChromeOptions +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.support.ui import WebDriverWait + try: from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_OAEP except: from Cryptodome.PublicKey import RSA from Cryptodome.Cipher import PKCS1_OAEP -from base64 import b64encode import requests from requests.adapters import HTTPAdapter @@ -28,10 +33,18 @@ "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", - "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", + "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", + "origin": "https://www.bestbuy.com", } -cookie = "" # Been manually adding the cookie string for testing. +options = Options() +options.page_load_strategy = "eager" +chrome_options = ChromeOptions() +chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"]) +chrome_options.add_experimental_option("useAutomationExtension", False) +prefs = {"profile.managed_default_content_settings.images": 2} +chrome_options.add_experimental_option("prefs", prefs) +chrome_options.add_argument("user-data-dir=.profile") class BestBuyHandler: @@ -40,10 +53,11 @@ def __init__(self, sku_id): self.sku_id = sku_id self.session = requests.Session() self.auto_buy = False + self.account = {"username": "", "password": ""} adapter = HTTPAdapter( max_retries=Retry( - total=10, + total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504], method_whitelist=["HEAD", "GET", "OPTIONS", "POST"], @@ -55,10 +69,84 @@ def __init__(self, sku_id): response = self.session.get( BEST_BUY_PDP_URL.format(sku=self.sku_id), headers=DEFAULT_HEADERS ) - log.info(response.status_code) + 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(response.status_code) + 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(): @@ -110,34 +198,40 @@ def auto_checkout(self): self.auto_add_to_cart() self.start_checkout() self.submit_shipping() - webbrowser.open_new("https://www.bestbuy.com/checkout/r/payment") - # self.submit_payment(tas_data) + 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", - "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; charset=UTF-8", + "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, - "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", - "cookie": cookie, + "Content-Length": str(len(json.dumps(body))), } - body = {"items": [{"skuId": self.sku_id}]} - + # [ + # 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 + 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()) @@ -149,7 +243,6 @@ def start_checkout(self): "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", - "cookie": cookie, } while True: log.info("Starting Checkout") @@ -159,9 +252,16 @@ def start_checkout(self): 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("Started Checkout") + 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) @@ -169,81 +269,48 @@ def start_checkout(self): def submit_shipping(self): log.info("Starting Checkout") headers = { - "accept": "application/com.bestbuy.order+json", + "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/checkout/r/fulfillment", + "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", - "cookie": cookie + "x-order-id": self.order_id, } while True: log.info("Submitting Shipping") - body = {"phoneNumber": "", "smsNotifyNumber": ""} - response = self.session.patch( - "https://www.bestbuy.com/checkout/orders/{order_id}/".format(order_id=self.order_id), + 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() - if response.status_code == 200 and response_json["id"] == self.order_id: - order_state = response_json["state"] - log.info(f"Order State: {order_state}") + 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(response.text) log.info("Error Submitting Shipping") def submit_payment(self, tas_data): - card = { # https://developers.bluesnap.com/docs/test-credit-cards - 'card_number': "4263982640269299", - 'card_month': '04', - 'card_year': '2023', - 'cvv': '738' - } - key = RSA.importKey(tas_data["publicKey"]) - cipher = PKCS1_OAEP.new(key) - encrypted_card = b64encode(cipher.encrypt(("00926999" + card['card_number']).encode("utf-8"))).decode("utf-8") - zero_string = "" - for i in range(len(card['card_number']) - 10): - zero_string += "0" - self.bin_number = card['card_number'][:6] - encrypted_card += ":3:" + tas_data["keyId"] + ":" + self.bin_number + zero_string + card['card_number'][-4:] - body = { - "billingAddress": { - "country": "US", - "useAddressAsBilling": True, - "middleInitial": "", - "lastName": "", - "isWishListAddress": False, - "city": "", - "state": "", - "firstName": "", - "addressLine1": "", - "addressLine2": "", - "dayPhone": "", - "postalCode": "", - "userOverridden": False, - }, - "creditCard": { - "hasCID": False, - "isNewCard": False, - "invalidCard": False, - "number": encrypted_card, - "binNumber": self.bin_number, - "isVisaCheckout": False, - "isCustomerCard": False, - "isPWPRegistered": False, - "saveToProfile": False, - "cid": card["cvv"], - "type": "VISA", - "virtualCard": False, - "expiration": {"month": card["card_month"], "year": card["card_year"]}, - }, + "items": [ + { + "id": self.item_id, + "type": "DEFAULT", + "selectedFulfillment": {"shipping": {"address": {}}}, + "giftMessageSelected": False, + } + ] } headers = { "accept": "application/com.bestbuy.order+json", @@ -254,11 +321,18 @@ def submit_payment(self, tas_data): "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", - "cookie": cookie } - r = self.session.patch("https://www.bestbuy.com/checkout/orders/{}/paymentMethods".format(self.order_id), - json=body, headers=headers) - + 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)