-
Notifications
You must be signed in to change notification settings - Fork 29
/
amazon_gift_card_reload.py
275 lines (224 loc) · 14.2 KB
/
amazon_gift_card_reload.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
import logging
import random
import time
from selenium import common
from selenium.common.exceptions import TimeoutException, ElementClickInterceptedException, WebDriverException, \
ElementNotInteractableException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
import utils
from result import Result
LOGGER = logging.getLogger('debbit')
def web_automation(driver, merchant, amount):
driver.get('https://www.amazon.com/gp/product/B086KKT3RX')
WebDriverWait(driver, 90).until(expected_conditions.element_to_be_clickable((By.ID, "gcui-asv-reload-buynow-button")))
for i in range(300):
if driver.find_element_by_id("gcui-asv-reload-buynow-button").text == 'Buy Now': # wait for 'Loading...' text to turn into 'Buy Now'
break
time.sleep(0.1)
time.sleep(1 + random.random() * 2) # slow down automation randomly to help avoid bot detection
driver.find_element_by_id('gcui-asv-reload-form-custom-amount').send_keys(utils.cents_to_str(amount))
time.sleep(1 + random.random() * 2) # slow down automation randomly to help avoid bot detection
driver.find_element_by_id("gcui-asv-reload-buynow-button").click()
WebDriverWait(driver, 90).until(utils.AnyExpectedCondition(
expected_conditions.element_to_be_clickable((By.ID, 'ap_email')), # first time login
expected_conditions.element_to_be_clickable((By.XPATH, "//*[contains(text(),'" + merchant.usr + "')]")), # username found on login page
# Already logged in
expected_conditions.element_to_be_clickable((By.XPATH, "//*[contains(text(),'Order Summary')]")), # Checkout page
expected_conditions.element_to_be_clickable((By.XPATH, "//*[contains(text(),'a payment method')]")) # Another version of the checkout page
))
if not driver.find_elements_by_xpath("//*[contains(text(),'Order Summary')]") and not driver.find_elements_by_xpath("//*[contains(text(),'a payment method')]"): # Not in checkout, so we did not auto login. Finish login flow.
if driver.find_elements_by_xpath("//*[contains(text(),'" + merchant.usr + "')]"):
driver.find_element_by_xpath("//*[contains(text(),'" + merchant.usr + "')]").click() # click username in case we're on the Switch Accounts page
WebDriverWait(driver, 30).until(expected_conditions.element_to_be_clickable((By.ID, 'signInSubmit')))
time.sleep(1 + random.random() * 2)
if driver.find_elements_by_id('ap_email'): # if first run, fill in email. If subsequent run, nothing to fill in
try:
driver.find_element_by_id('ap_email').send_keys(merchant.usr)
time.sleep(1 + random.random() * 2)
except ElementNotInteractableException: # Sometimes this field is prefilled with Firstname Lastname and does not accept input
pass
if driver.find_elements_by_id('continue'): # a/b tested new UI flow
driver.find_element_by_id('continue').click()
WebDriverWait(driver, 5).until(expected_conditions.element_to_be_clickable((By.ID, 'ap_password')))
time.sleep(1 + random.random() * 2)
if driver.find_elements_by_name('rememberMe'):
time.sleep(1 + random.random() * 2)
driver.find_element_by_name('rememberMe').click()
driver.find_element_by_id('ap_password').send_keys(merchant.psw)
time.sleep(1 + random.random() * 2)
driver.find_element_by_id('signInSubmit').click()
time.sleep(1 + random.random() * 2)
handle_anti_automation_challenge(driver, merchant)
try: # Push Notification / Email MFA
WebDriverWait(driver, 5).until(expected_conditions.element_to_be_clickable((By.XPATH, "//*[contains(text(),'approve the notification')]")))
if driver.find_elements_by_xpath("//*[contains(text(),'approve the notification')]"):
LOGGER.info('\n')
LOGGER.info('Please approve the Amazon login notification sent to your email or phone. Debbit will wait up to 3 minutes.')
for i in range(180): # Wait for up to 3 minutes for user to approve login notification
if not driver.find_elements_by_xpath("//*[contains(text(),'approve the notification')]"):
break
time.sleep(1)
except TimeoutException:
pass
try: # OTP text message
WebDriverWait(driver, 5).until(expected_conditions.element_to_be_clickable((By.XPATH, "//*[contains(text(),'phone number ending in')]")))
if driver.find_elements_by_id('auth-mfa-remember-device'):
driver.find_element_by_id('auth-mfa-remember-device').click()
sent_to_text = driver.find_element_by_xpath("//*[contains(text(),'phone number ending in')]").text
LOGGER.info(sent_to_text)
LOGGER.info('Enter OTP here:')
otp = input()
driver.find_element_by_id('auth-mfa-otpcode').send_keys(otp)
time.sleep(1 + random.random() * 2)
driver.find_element_by_id('auth-signin-button').click()
time.sleep(1 + random.random() * 2)
except TimeoutException:
pass
try: # OTP email validation
WebDriverWait(driver, 5).until(expected_conditions.element_to_be_clickable((By.XPATH, "//*[contains(text(),'One Time Pass')]")))
otp_email = True
except TimeoutException:
otp_email = False
try:
driver.find_element_by_xpath("//*[contains(text(),'one-time pass')]").click()
time.sleep(1 + random.random() * 2)
otp_email = True
except common.exceptions.NoSuchElementException:
pass
if otp_email:
if driver.find_elements_by_id('continue'):
driver.find_element_by_id('continue').click()
time.sleep(1 + random.random() * 2)
handle_anti_automation_challenge(driver, merchant)
try: # User may have manually advanced to gift card screen or stopped at OTP input. Handle OTP input if on OTP screen.
WebDriverWait(driver, 5).until(expected_conditions.element_to_be_clickable((By.XPATH, "//*[contains(text(),'Enter OTP')]")))
sent_to_text = driver.find_element_by_xpath("//*[contains(text(),'@')]").text
LOGGER.info(sent_to_text)
LOGGER.info('Enter OTP here:')
otp = input()
elem = driver.find_element_by_xpath("//input")
elem.send_keys(otp)
time.sleep(1 + random.random() * 2)
elem.send_keys(Keys.TAB)
time.sleep(1 + random.random() * 2)
elem.send_keys(Keys.ENTER)
time.sleep(1 + random.random() * 2)
except TimeoutException:
pass
try:
WebDriverWait(driver, 5).until(expected_conditions.element_to_be_clickable((By.XPATH, "//*[contains(text(),'Not now')]")))
driver.find_element_by_xpath("//*[contains(text(),'Not now')]").click()
time.sleep(1 + random.random() * 2)
except TimeoutException: # add mobile number page
pass
# Now expecting to be on checkout page with debit card selection present
WebDriverWait(driver, 30).until(utils.AnyExpectedCondition(
expected_conditions.element_to_be_clickable((By.XPATH, "//*[contains(text(),'Order Summary')]")), # Checkout page
expected_conditions.element_to_be_clickable((By.XPATH, "//*[contains(text(),'a payment method')]")) # Another version of the checkout page
))
if driver.find_elements_by_id('payChangeButtonId'): # expand list of cards
time.sleep(1 + random.random() * 2)
driver.find_element_by_id('payChangeButtonId').click()
WebDriverWait(driver, 10).until(expected_conditions.element_to_be_clickable((By.XPATH, "//span[contains(text(),'ending in " + merchant.card[-4:] + "')]")))
if driver.find_elements_by_id('payment-change-link'): # expand list of cards if prior element did not exist
time.sleep(1 + random.random() * 2)
driver.find_element_by_id('payment-change-link').click()
WebDriverWait(driver, 10).until(expected_conditions.element_to_be_clickable((By.XPATH, "//span[contains(text(),'ending in " + merchant.card[-4:] + "')]")))
card_selected = False
for element in driver.find_elements_by_xpath("//span[contains(text(),'ending in " + merchant.card[-4:] + "')]"):
try: # Amazon has redundant non-clickable elements. This will try each one until one works.
time.sleep(1 + random.random() * 2)
element.click()
card_selected = True
break
except WebDriverException:
pass
if not card_selected:
raise Exception('Unable to find or unable to click on card that has last 4 digits matching config file card.')
if driver.find_elements_by_id('orderSummaryPrimaryActionBtn'):
time.sleep(1 + random.random() * 2)
driver.find_element_by_id('orderSummaryPrimaryActionBtn').click() # Click "Use this payment method" button
else: # Find Continue button. There are also non clickable spans with 'Continue' in them so try all of them until one works.
for element in driver.find_elements_by_xpath("//span[contains(text(),'Continue')]"):
try:
time.sleep(1 + random.random() * 2)
element.find_element_by_xpath('../..').click() # the grandparent element of the text is the clickable Continue button
break
except Exception:
pass
WebDriverWait(driver, 10).until(utils.AnyExpectedCondition(
expected_conditions.element_to_be_clickable((By.ID, 'submitOrderButtonId')), # "Place your order" button showing, card ready to be used
expected_conditions.element_to_be_clickable((By.ID, 'placeYourOrder')), # Other checkout page "Place your order" button showing, card ready to be used
expected_conditions.element_to_be_clickable((By.XPATH, "//input[@placeholder='ending in " + merchant.card[-4:] + "']")) # Verify card flow
))
if driver.find_elements_by_xpath("//input[@placeholder='ending in " + merchant.card[-4:] + "']"): # Verify card flow
elem = driver.find_element_by_xpath("//input[@placeholder='ending in " + merchant.card[-4:] + "']")
time.sleep(1 + random.random() * 2)
elem.send_keys(merchant.card)
time.sleep(1 + random.random() * 2)
elem.send_keys(Keys.TAB)
time.sleep(1 + random.random() * 2)
elem.send_keys(Keys.ENTER)
time.sleep(10 + random.random() * 2)
if driver.find_elements_by_id('orderSummaryPrimaryActionBtn'):
driver.find_element_by_id('orderSummaryPrimaryActionBtn').click() # Click "Use this payment method" button
else: # Find Continue text, the grandparent element of the text is the clickable Continue button
driver.find_element_by_xpath("//span[contains(text(),'Continue')]").find_element_by_xpath('../..').click()
WebDriverWait(driver, 10).until(utils.AnyExpectedCondition(
expected_conditions.element_to_be_clickable((By.ID, 'submitOrderButtonId')), # "Place your order" button showing, card ready to be used
expected_conditions.element_to_be_clickable((By.ID, 'placeYourOrder')), # Other checkout page "Place your order" button showing, card ready to be used
))
time.sleep(1 + random.random() * 2)
if not is_order_total_correct(driver, amount):
return Result.unverified
if driver.find_elements_by_id('submitOrderButtonId'):
time.sleep(1 + random.random() * 2)
driver.find_element_by_id('submitOrderButtonId').click() # Click "Place your order" button
else:
time.sleep(1 + random.random() * 2)
driver.find_element_by_id('placeYourOrder').click() # Other checkout page click "Place your order" button
try:
WebDriverWait(driver, 30).until(expected_conditions.element_to_be_clickable((By.XPATH, "//*[contains(text(), 'your order has been placed') or contains(text(),'Order placed')]")))
except TimeoutException:
LOGGER.error('Clicked "Place your order" button, but unable to confirm if order was successful.')
return Result.unverified
if driver.find_elements_by_xpath("//*[contains(text(), 'your order has been placed') or contains(text(),'Order placed')]"):
return Result.success
else:
LOGGER.error('Clicked "Place your order" button, but unable to confirm if order was successful.')
return Result.unverified
def handle_anti_automation_challenge(driver, merchant):
try:
WebDriverWait(driver, 5).until(expected_conditions.element_to_be_clickable((By.XPATH, "//*[contains(text(),'nter the characters')]")))
time.sleep(1 + random.random() * 2)
if driver.find_elements_by_id('ap_password'):
driver.find_element_by_id('ap_password').send_keys(merchant.psw)
LOGGER.info('amazon captcha detected')
input('''
Anti-automation captcha detected. Please follow these steps, future runs shouldn't need captcha input unless you set "use_cookies: no" in config.txt.
1. Open the Firefox window that debbit created.
2. Input the captcha / other anti-automation challenges.
3. You should now be on the gift card reload page
4. Click on this terminal window and hit "Enter" to continue running debbit.
''')
except TimeoutException:
pass
def is_order_total_correct(driver, amount):
elements_to_check = []
try:
elements_to_check.append(driver.find_element_by_id('subtotals-marketplace-spp-bottom').text)
except common.exceptions.NoSuchElementException:
pass
try:
elements_to_check.append(driver.find_element_by_class_name('grand-total-price').text)
except common.exceptions.NoSuchElementException:
pass
expected_order_total = '$' + utils.cents_to_str(amount)
for element in elements_to_check:
if expected_order_total in element:
return True
LOGGER.error('Unable to verify order total is correct, not purchasing. Could not find expected amount ' + expected_order_total + ' in ' + str(elements_to_check))
return False