Skip to content

Commit

Permalink
init: Hits the stock API every 5 seconds and then loads up a page wit…
Browse files Browse the repository at this point in the history
…h the item added to cart.

Signed-off-by: Hari-Nagarajan <[email protected]>
  • Loading branch information
Hari-Nagarajan committed Sep 18, 2020
1 parent 2300f5f commit 0099d52
Show file tree
Hide file tree
Showing 10 changed files with 284 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/dist/
/.idea/
15 changes: 15 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]
pyinstaller = "*"
black = "*"

[packages]
requests = "*"
click = "*"

[requires]
python_version = "3.7"
111 changes: 111 additions & 0 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from cli import cli

if __name__ == "__main__":
cli.main()
33 changes: 33 additions & 0 deletions app.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- mode: python ; coding: utf-8 -*-

block_cipher = None


a = Analysis(['app.py'],
pathex=['C:\\Users\\hari\\PycharmProjects\\nvidia-bot'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='app',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True )
Empty file added cli/__init__.py
Empty file.
19 changes: 19 additions & 0 deletions cli/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import click

from cli.gpu import GPU
from stores.nvidia import NvidiaBuyer


@click.group()
def main():
pass


@click.command()
@click.argument("gpu", type=GPU())
def buy(gpu):
nv = NvidiaBuyer()
nv.buy(gpu)


main.add_command(buy)
17 changes: 17 additions & 0 deletions cli/gpu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import click

from stores.nvidia import GPU_DISPLAY_NAMES


class GPU(click.ParamType):
name = "api-key"

def convert(self, value, param, ctx):
if value.upper() not in GPU_DISPLAY_NAMES.keys():
self.fail(
f"{value} is not a valid GPU, valid GPUs are {list(GPU_DISPLAY_NAMES.keys())}",
param,
ctx,
)

return value.upper()
Empty file added stores/__init__.py
Empty file.
83 changes: 83 additions & 0 deletions stores/nvidia.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import logging
import webbrowser

import requests

log = logging.getLogger(__name__)
formatter = logging.Formatter(
"%(asctime)s : %(message)s : %(levelname)s -%(name)s", datefmt="%d%m%Y %I:%M:%S %p"
)
handler = logging.StreamHandler()
handler.setFormatter(formatter)
log.setLevel(10)
log.addHandler(handler)

DIGITAL_RIVER_OUT_OF_STOCK_MESSAGE = "PRODUCT_INVENTORY_OUT_OF_STOCK"
DIGITAL_RIVER_API_KEY = "9485fa7b159e42edb08a83bde0d83dia"
DIGITAL_RIVER_PRODUCT_LIST_URL = "https://api.digitalriver.com/v1/shoppers/me/products"
DIGITAL_RIVER_STOCK_CHECK_URL = "https://api.digitalriver.com/v1/shoppers/me/products/{product_id}/inventory-status?"

NVIDIA_CART_URL = "https://store.nvidia.com/store/nvidia/en_US/buy/productID.{product_id}/clearCart.yes/nextPage.QuickBuyCartPage"

GPU_DISPLAY_NAMES = {
"2060S": "NVIDIA GEFORCE RTX 2060 SUPER",
"3080": "NVIDIA GEFORCE RTX 3080",
"3090": "NVIDIA GEFORCE RTX 3090",
}


def add_to_cart(product_id):
log.info(f"Adding {product_id} to cart!")
webbrowser.open_new(NVIDIA_CART_URL.format(product_id=product_id))


def is_in_stock(product_id):
payload = {
"apiKey": DIGITAL_RIVER_API_KEY,
}

url = DIGITAL_RIVER_STOCK_CHECK_URL.format(product_id=product_id)

log.debug(f"Calling {url}")
response = requests.get(url, headers={"Accept": "application/json"}, params=payload)
log.debug(f"Returned {response.status_code}")
response_json = response.json()
product_status_message = response_json["inventoryStatus"]["status"]
log.info(f"Stock status is {product_status_message}")
return product_status_message != DIGITAL_RIVER_OUT_OF_STOCK_MESSAGE


class NvidiaBuyer:
def __init__(self):
self.product_data = {}
self.get_product_ids()
print(self.product_data)

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",
}
response = requests.get(
url, headers={"Accept": "application/json"}, params=payload
)

log.debug(response.status_code)
response_json = response.json()
for product_obj in response_json["products"]["product"]:
if product_obj["displayName"] in GPU_DISPLAY_NAMES.values():
self.product_data[product_obj["displayName"]] = {
"id": product_obj["id"],
"price": product_obj["pricing"]["formattedListPrice"],
}
if response_json["products"].get("nextPage"):
self.get_product_ids(url=response_json["products"]["nextPage"]["uri"])

def buy(self, gpu):
product_id = self.product_data.get(GPU_DISPLAY_NAMES[gpu])["id"]
log.info(f"Checking stock for {GPU_DISPLAY_NAMES[gpu]}...")
while not is_in_stock(product_id):
sleep(5)
add_to_cart(product_id)

0 comments on commit 0099d52

Please sign in to comment.