Skip to content

Commit

Permalink
Update GameStop provider
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenmirabito committed Nov 17, 2019
1 parent c5d2988 commit ecc6308
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 63 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Automation tool for checking the balance of gift cards issued by various provide
## Supported Providers
- [Blackhawk / Gift Card Mall / Simon / 5Back](https://mygift.giftcardmall.com) - `blackhawk` <sup>CAPTCHA</sup>
- [Spafinder](https://www.spafinder.com/pages/card-balance-inquiry/vpln) - `spafinder` <sup>CAPTCHA</sup>
- [GameStop](https://www.gamestop.com/profiles/valuelookup.aspx) - `gamestop` <sup>CAPTCHA</sup>
- [GameStop](https://www.gamestop.com/giftcards/) - `gamestop` <sup>CAPTCHA</sup>
- [Best Buy](https://www.bestbuy.com/gift-card-balance) - `bestbuy`
- [Home Depot](https://www.homedepot.com/mycheckout/giftcard) - `homedepot` <sup>CAPTCHA</sup>

Expand Down
4 changes: 2 additions & 2 deletions balance_check/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
USER_AGENT = env.get(
"USER_AGENT",
(
"Mozilla/5.0 (Macintosh;"
"Intel Mac OS X 10.14;"
"Mozilla/5.0 (Macintosh; "
"Intel Mac OS X 10.14; "
"rv:64.0) Gecko/20100101 Firefox/64.0"
),
)
123 changes: 63 additions & 60 deletions balance_check/providers/gamestop.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,55 @@
import requests
import json
from urllib import request
from urllib.parse import urlencode
from bs4 import BeautifulSoup
from http.cookiejar import CookieJar
from balance_check import logger, config
from balance_check.utils import deep_get
from balance_check.utils.captcha import CaptchaSolver
from balance_check.providers import BalanceCheckProvider
from balance_check.validators.gift_card import Merchant, GiftCardSchema

HEADERS = {
"user-agent": config.USER_AGENT,
}


class GameStop(BalanceCheckProvider):
def __init__(self):
super().__init__()

self.website_url = "https://www.gamestop.com/profiles/valuelookup.aspx"
self.website_url = "https://www.gamestop.com/giftcards/"
self.api_endpoint = "https://www.gamestop.com/on/demandware.store/Sites-gamestop-us-Site/default/GiftCard-BalanceCheck"
self.schema = GiftCardSchema(Merchant.GameStop)
self.max_workers = 1 # For some reason does not work properly multithreaded

def scrape(self, fields):
session = requests.Session()
session.headers.update({"User-Agent": config.USER_AGENT})
def scrape(self, **kwargs):
cookies = CookieJar()
opener = request.build_opener(request.HTTPCookieProcessor(cookies))

logger.info("Fetching balance check page")

resp = session.get(self.website_url)
if resp.status_code != 200:
# Use urllib directly as requests gets blocked
req = request.Request(self.website_url, headers=HEADERS)
resp = opener.open(req)

if resp.status != 200:
raise RuntimeError(
f"Failed to GET website (status code {resp.status_code})"
f"Failed to get GameStop website (status code {resp.status})"
)

page_html = BeautifulSoup(resp.content, features="html.parser")
form = page_html.find("form")
if not form:
raise RuntimeError("Unable to find balance check form")

endpoint = f"{self.website_url}{form['action']}"
page_html = BeautifulSoup(resp.read(), features="html.parser")

# These fields are present on GS balance check page, does not work without including them
fields["__EVENTTARGET"] = ""
fields["__EVENTARGUMENT"] = ""
fields["__LASTFOCUS"] = ""
fields["__VIEWSTATE"] = page_html.find("input", id="__VIEWSTATE")["value"]
fields["__VIEWSTATEGENERATOR"] = page_html.find(
"input", id="__VIEWSTATEGENERATOR"
)["value"]
recaptcha_el = page_html.find("div", {"data-sitekey": True})
if not recaptcha_el:
raise RuntimeError("Unable to find reCAPTCHA on page")

recaptcha_field = page_html.find("div", class_="g-recaptcha")
if not recaptcha_field:
raise RuntimeError("Unable to find reCAPTCHA")
csrf_el = page_html.find("input", {"name": "csrf_token"})
if not csrf_el:
raise RuntimeError("Unable to find CSRF on page")

site_key = recaptcha_field["data-sitekey"]
site_key = recaptcha_el["data-sitekey"]
csrf_token = csrf_el["value"]

logger.info("Solving reCAPTCHA (~30s)")

Expand All @@ -57,51 +60,51 @@ def scrape(self, fields):
f"Unable to solve reCAPTCHA ({captcha_resp['errorDescription']})"
)

fields["g-recaptcha-response"] = captcha_resp["solution"]["gRecaptchaResponse"]
payload = {
"dwfrm_giftCard_balance_accountNumber": kwargs["card_number"],
"dwfrm_giftCard_balance_pinNumber": kwargs["pin"],
"g-recaptcha-response": captcha_resp["solution"]["gRecaptchaResponse"],
"csrf_token": csrf_token,
}

logger.info("Fetching card balance")
logger.info("Fetching balance from API")

form_resp = session.post(endpoint, data=fields)
if form_resp.status_code != 200:
raise RuntimeError(
f"Failed to retrieve card balance (status code {form_resp.status_code})"
try:
req = request.Request(self.api_endpoint, headers=HEADERS)
data = urlencode(payload).encode("utf-8")
req.add_header(
"Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"
)
req.add_header("Content-Length", len(data))
resp = opener.open(req, data)

balance_html = BeautifulSoup(form_resp.content, features="html.parser")
if resp.status != 200:
raise RuntimeError(f"Invalid API response (status code {resp.status})")

try:
avail_balance = balance_html.find("span", class_="balancePrice").text
except:
# GS prunes old depleted cards from its system and throws an 'invalid' error
# Set these as -1 to notate they gave invalid error but are likely actually zero balance
if balance_html.find(text="The Gift Card number entered is invalid."):
avail_balance = "-1"
elif balance_html.find(text="The PIN number entered is invalid."):
raise RuntimeError("Invalid PIN")
elif balance_html.find(text="The code you entered is invalid."):
raise RuntimeError("Invalid CAPTCHA answer supplied")
else:
result = json.loads(
resp.read().decode(resp.info().get_param("charset") or "utf-8")
)
errors = deep_get(result, "error")
if errors:
err = errors[0]
raise RuntimeError(f"Failed to retrieve balance from API: {err}")

balance = deep_get(result, "balance")
if balance is None:
raise RuntimeError(
"Could not find balance on retrieved page for unknown reason"
f"Invalid API response: unable to find required key in JSON response"
)

logger.info(f"Success! Card balance: {avail_balance}")
logger.info(f"Success! Card balance: ${balance}")

return {"balance": avail_balance}
return {
"balance": f"${balance}",
}
except json.JSONDecodeError:
raise RuntimeError("Failed to parse API response as JSON")

def check_balance(self, **kwargs):
if self.validate(kwargs):
logger.info(f"Checking balance for card: {kwargs['card_number']}")

form_inputs = {
"ctl00$ctl00$BaseContentPlaceHolder$mainContentPlaceHolder$CardNumberTextBox": kwargs[
"card_number"
],
"ctl00$ctl00$BaseContentPlaceHolder$mainContentPlaceHolder$PinTextBox": kwargs[
"pin"
],
}

return self.scrape(form_inputs)
# else:
# Invalid
return self.scrape(card_number=kwargs["card_number"], pin=kwargs["pin"])

0 comments on commit ecc6308

Please sign in to comment.