From edc921e285436134009868dd4a3145a9d6458c6a Mon Sep 17 00:00:00 2001 From: Alain Rodriguez Date: Fri, 25 Jan 2019 01:09:16 +0000 Subject: [PATCH 1/5] Adding click to handle arguments + small changes --- README.md | 43 +++++- buyKim.py | 366 +++++++++++++++++++++++------------------------ requirements.txt | 5 +- 3 files changed, 222 insertions(+), 192 deletions(-) diff --git a/README.md b/README.md index c7b2dc1..79d3401 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # Rationale Recently I wanted to purchase a Kimsufi server. However, I was not the only one to want one and OVH has no queuing mechanim, leaving -me the only option of reloading the availability page waiting for an +me the only option of reloading the availability page waiting for an opportunity to rent one. Pretty quickly, I came up with this script: an **automated tool** to @@ -14,21 +14,50 @@ wasting your precious time waiting! # Usage - Install requirements: `pip install -r requirements.txt` + +- One of the dependency is `Selenium` that depends on drivers: https://github.com/SeleniumHQ/selenium/blob/master/py/docs/source/index.rst#user-content-drivers. + +On Mac to make this step easier, run: +``` +brew install geckodriver +``` + - Connect your OVH account to PayPal (this seemed the best option to avoid handling Credit Card data, but PR welcome if you want to do otherwise!) -- Set the environment variables `OVH_USERNAME` and `OVH_PASSWORD` (they are used to automate the purchase itself) -- Set `ref_product` to the product you want (default: `150sk22`) and `ref_zone` to the zone you want to rent it in (default: `bhs`, Beauharnois datacenter) + - Run `python buyKim.py` -- ??? +``` +$ python buyKim.py --help +Usage: buyKim.py [OPTIONS] + +Options: + -t, --timeout-conn INTEGER Maximum time in seconds to wait for webservice + answer. [default: 5] + -i, --interval FLOAT Minimum interval in seconds between two requests + [default: 7.5] + -f, --product-family TEXT The family of servers (ie. "Kimsufi"/"So you + Start") [default: Kimsufi] + -p, --ref-product TEXT Reference of the server (ie 1801sk12 for KS1, + 1801sys29 for some soYouStart servers [default: + 1801sk12] + -z, --ref-zones TEXT Data center short name(s) (ie "-z gra -z rbx") + [default: gra, rbx, lon, fra] + --ovh-user TEXT + --ovh-pass TEXT + --debug / --no-debug Debug mode, disable by default. Add --debug flag + to enable + --help Show this message and exit. +``` + - Profit! # Architecture The script is split in two parts: -- The first part uses `requests` to poll OVH's availability webservice, -and parses its response to find the product you want. When the response +- The first part uses `requests` to poll OVH's availability webservice, +and parses its response to find the product you want. When the response describes your product as available, the second part of the script kicks in. -- The second part uses `selenium` for opening the listing page, then selects +- The second part uses `selenium` for opening the listing page, then selects your product, injects some Angular.JS-specific JavaScript for selecting your datacenter, waits for PayPal to load, and clicks on Purchase! diff --git a/buyKim.py b/buyKim.py index 69634de..2a3c564 100755 --- a/buyKim.py +++ b/buyKim.py @@ -9,195 +9,195 @@ from selenium.webdriver.common.by import By from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC +import click from utils import zoom_out, screenshot_step -def print_and_log(message, level=logging.DEBUG, sep=' ', end='\n', flush=False): +def print_and_log(message, level=logging.INFO, sep=' ', end='\n', flush=False): print(message, sep=sep, end=end, flush=flush) logging.log(level, message) -MAX_REQ_TIMEOUT_READ = None - -MAX_REQ_TIMEOUT_CONN = 5 # Maximum time in seconds to wait for webservice answer -MIN_REQ_INTERVAL = 7.5 # Minimum interval in seconds between two requests (7.5 s.req = 480 req/h < 500 req/h) -DEBUG = True - -page_title = "Kimsufi" # "So you Start" -# ref_product = "143sys2" -ref_product = "150sk22" if DEBUG else "150sk20" -ref_zone = "gra" if DEBUG else "bhs" -url_availability = "https://ws.ovh.com/dedicated/r2/ws.dispatcher/getAvailability2" -not_available_terms = ['unknown', 'unavailable'] - -time_run = datetime.now().strftime("%y-%m-%d %H-%M-%f") - -screenshot_dir = os.getenv("SCREENSHOT_DIR", os.path.abspath("screens")) + "\\" -log_dir = os.getenv("LOG_DIR", os.getcwd()) -if not os.path.exists(screenshot_dir): - os.makedirs(screenshot_dir) -if not os.path.exists(log_dir): - os.makedirs(log_dir) -log_filename = os.path.join(log_dir, "buyKim.log") - -print("Log filename: %s" % log_filename) -logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', filename=log_filename, level=logging.DEBUG) -logging.getLogger("requests").setLevel(logging.WARNING) - -print_and_log("Saving screenshots in %s" % screenshot_dir, logging.INFO) -screen_prefix = screenshot_dir + time_run - -ovh_user = os.environ["OVH_USERNAME"] -ovh_pass = os.environ["OVH_PASSWORD"] -print_and_log("Loaded environment: Connecting as %s with password %s..." % (ovh_user, ovh_pass[:5])) - -data = "" -available = False -while not available: - success = False - time_start = time.time() - time_elapsed = 0 - time_run_str = datetime.now().strftime("%y/%m/%d %H:%M:%S") - log_msg = "Requesting... " - print("%s | " % time_run_str + log_msg, flush=True, end=' ') +@click.command() +@click.option('--timeout-conn', '-t', default=5, show_default=True, help='Maximum time in seconds to wait for webservice answer.') +@click.option('--interval', '-i', default=7.5, show_default=True, help='Minimum interval in seconds between two requests') +@click.option('--product-family', '-f', default="Kimsufi", show_default=True, help='The family of servers (ie. "Kimsufi"/"So you Start")') +@click.option('--ref-product', '-p', default="1801sk12", show_default=True, help='Reference of the server (ie 1801sk12 for KS1, 1801sys29 for some soYouStart servers') +@click.option('--ref-zones', '-z', default=["gra","rbx","lon","fra"], show_default=True, multiple=True, help='Data center short name(s) (ie "-z gra -z rbx")') +@click.option('--ovh-user', prompt=True, hide_input=False) +@click.option('--ovh-pass', prompt=True, hide_input=True) +@click.option('--debug/--no-debug', default=False, help='Debug mode, disable by default. Add --debug flag to enable') +def main(timeout_conn, interval, product_family, ref_product, ref_zones, ovh_user, ovh_pass, debug): + MAX_REQ_TIMEOUT_READ = None + + url_availability = "https://ws.ovh.com/dedicated/r2/ws.dispatcher/getAvailability2" + not_available_terms = ['unknown', 'unavailable'] + + time_run = datetime.now().strftime("%y-%m-%d %H-%M-%f") + + screenshot_dir = os.getenv("SCREENSHOT_DIR", os.path.abspath("screens")) + log_dir = os.getenv("LOG_DIR", os.getcwd()) + if not os.path.exists(screenshot_dir): + os.makedirs(screenshot_dir) + if not os.path.exists(log_dir): + os.makedirs(log_dir) + log_filename = os.path.join(log_dir, "buyKim.log") + + print("Log filename: {}".format(log_filename)) + logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', filename=log_filename, level=logging.DEBUG) + logging.getLogger("requests").setLevel(logging.WARNING) + + print_and_log("Saving screenshots in {}".format(screenshot_dir)) + screen_prefix = screenshot_dir + time_run + + available = False + while not available: + success = False + time_start = time.time() + time_elapsed = 0 + time_run_str = datetime.now().strftime("%y/%m/%d %H:%M:%S") + log_msg = "Requesting... " + print("{} | {} ".format(time_run_str, log_msg), flush=True) + try: + request_ws = requests.get(url_availability, timeout=(timeout_conn, MAX_REQ_TIMEOUT_READ)) + data = request_ws.json() + + if 'answer' in data: + if 'availability' in data['answer']: + available_servers = data['answer']['availability'] + found_product = False + for line in available_servers: + if line['reference'] == ref_product: + found_product = True + + found_zone = None + msg_model = "" + msg_zones = "zones:" + zone_avails = [] + for zone in line['zones']: + availability = zone['availability'] + zone_name = zone['zone'] + zone_avails.append(zone_name + "=" + availability) + if zone_name in ref_zones: + found_zone = zone_name + success = True + available = availability not in not_available_terms + msg_model = 'The status of model "{}" in dc "{}" is {}'.format(ref_product, zone_name, availability) + log_msg += msg_model + print_and_log(msg_model) + if available: + break + + print_and_log("None of the zones was available ({})".format(", ".join(zone_avails))) + if not found_zone: + print_and_log("None of the data center was found for product {}.".format(', '.join(ref_zones), ref_product)) + if not found_product: + print_and_log("No data about product {}.".format(ref_product)) + else: + print_and_log("No answer in ws data.") + except TimeoutError: + print_and_log("Timeout while fetching webservice.") + except Exception as e: + print_and_log(str(type(e)) + " while parsing: " + str(e.args) + ' | ' + "Data: " + str(data)) + with open(log_filename, mode='a') as f: + f.write("\n" + "-" * 60) + traceback.print_exc(file=f) + f.write("-" * 60 + "\n") + while time_elapsed < interval: + time_end = time.time() + time_elapsed = time_end - time_start + time.sleep(1) + msg_time = " (time: %f)" % time_elapsed + if success: + print(msg_time) + logging.log(logging.DEBUG, log_msg + msg_time) + + print_and_log("Exited availability loop, {} is available in {}!".format(ref_product, found_zone)) + + driver = webdriver.Firefox() + driver.maximize_window() + available = False + driver.get("https://www.kimsufi.com/fr/commande/kimsufi.xml?reference=" + ref_product) + # driver.get("https://eu.soyoustart.com/fr/commande/soYouStart.xml?reference=143sys2") try: - request_ws = requests.get(url_availability, timeout=(MAX_REQ_TIMEOUT_CONN, MAX_REQ_TIMEOUT_READ)) - data = request_ws.json() - available_servers = data['answer']['availability'] - - if 'answer' in data: - if 'availability' in data['answer']: - found_product = False - for line in available_servers: - if line['reference'] == ref_product: - found_product = True - - found_zone = False - msg_model = "" - msg_zones = "zones:" - zone_avails = [] - for zone in line['zones']: - availability = zone['availability'] - zone_name = zone['zone'] - zone_avails.append(zone_name + "=" + availability) - if zone_name == ref_zone: - found_zone = True - success = True - available = availability not in not_available_terms - available_status = ("" if available else "not ") + "available" - msg_model = 'Model %s in dc %s is marked as %s -> %s' % ( - ref_product, ref_zone, availability, available_status) - log_msg += msg_model - if available: - print(msg_model, end="") - break - print("%s (%s)" % (msg_model, ", ".join(zone_avails)), end="") - - if not found_zone: - print_and_log("Zone %s was not found in data about product %s." % (ref_zone, ref_product)) - if not found_product: - print_and_log("No data about product %s." % ref_product) - else: - print_and_log("No answer in ws data.") - except TimeoutError: - print_and_log("Timeout while fetching webservice.") - except Exception as e: - print_and_log(str(type(e)) + " while parsing: " + str(e.args) + ' | ' + "Data: " + str(data)) - with open(log_filename, mode='a') as f: - f.write("\n" + "-" * 60) - traceback.print_exc(file=f) - f.write("-" * 60 + "\n") - while time_elapsed < MIN_REQ_INTERVAL: - time_end = time.time() - time_elapsed = time_end - time_start - time.sleep(1) - msg_time = " (time: %f)" % time_elapsed - if success: - print(msg_time) - logging.log(logging.DEBUG, log_msg + msg_time) - -print_and_log("Exited availability loop, %s is available in %s!" % (ref_product, ref_zone)) - -driver = webdriver.Firefox() -driver.maximize_window() -available = False -driver.get("https://www.kimsufi.com/fr/commande/kimsufi.xml?reference=" + ref_product) -# driver.get("https://eu.soyoustart.com/fr/commande/soYouStart.xml?reference=143sys2") -try: - assert page_title in driver.title - zoom_out(driver) - # Wait for the removal of waiting banner... - WebDriverWait(driver, 10).until_not(EC.presence_of_element_located( - (By.CSS_SELECTOR, "div.fixed-header div.alert.alert-info.ng-scope"))) - print_and_log("Page finished loading.") -except AssertionError: - print_and_log("The page didn't load correctly: " + driver.title) - - # if driver.find_element_by_class_name("alert-error") is None: - # available = True - -js_select_dhs = """var appDom = document.querySelector('#quantity-1'); -var appNg = angular.element(appDom); -var scope = appNg.scope(); -scope.config.datacenter = '""" + ref_zone + """'; -scope.$apply(); -""" - -print_and_log("Executing select script: `%s`." % js_select_dhs) -driver.execute_script(js_select_dhs) -print_and_log("Selected canadian datacenter.") - -css_label_existing = "span.existing label" -css_button_login = "div.customer-existing form span.last.ec-button span button" - -id_input_login = "existing-customer-login" -id_input_pass = "existing-customer-password" - -# Check existing customer -button_existing = driver.find_element_by_css_selector(css_label_existing) -print_and_log("Button found: " + str(button_existing)) -button_existing.click() -print_and_log("Clicked on existing customer.") - -screenshot_step(driver, screen_prefix, 1) -driver.execute_script("arguments[0].scrollIntoView(true);", button_existing) -screenshot_step(driver, screen_prefix, 2) -# Locate login inputs -input_login = driver.find_element_by_id(id_input_login) -input_pass = driver.find_element_by_id(id_input_pass) - -# Connect with given credentials -input_login.send_keys(ovh_user) -input_pass.send_keys(ovh_pass) -print_and_log("Wrote username and password into inputs.") -driver.find_element_by_css_selector(css_button_login).click() -print_and_log("Clicked on login button.") - -screenshot_step(driver, screen_prefix, 3) - -# Wait for means of payment to load -css_payment_valid = "div.payment-means-choice div.payment-means-list form span.selected input.custom-radio.ng-valid" -WebDriverWait(driver, 20).until(EC.presence_of_element_located( - (By.CSS_SELECTOR, css_payment_valid) -)) - -# Check inputs to accept contract conditions -css_input_cgv = "div.dedicated-contracts input#contracts-validation" -css_input_custom = "div.dedicated-contracts input#customConractAccepted" -css_button_purchase = "div.dedicated-contracts button.centered" -driver.find_element_by_css_selector(css_input_cgv).click() -driver.find_element_by_css_selector(css_input_custom).click() -print_and_log("Checked confirmation inputs.") - -# uncommented after numerous tests -if not DEBUG: - driver.find_element_by_css_selector(css_button_purchase).click() - print_and_log("Clicked on purchase button...") - -# Wait to realise what you've done -screenshot_step(driver, screen_prefix, 4) -time.sleep(30) -screenshot_step(driver, screen_prefix, 5) -if DEBUG: - driver.close() + assert product_family in driver.title + zoom_out(driver) + # Wait for the removal of waiting banner... + WebDriverWait(driver, 10).until_not(EC.presence_of_element_located( + (By.CSS_SELECTOR, "div.fixed-header div.alert.alert-info.ng-scope"))) + print_and_log("Page finished loading.") + except AssertionError: + print_and_log("The page didn't load correctly: " + driver.title) + + # if driver.find_element_by_class_name("alert-error") is None: + # available = True + + js_select_dhs = """var appDom = document.querySelector('#quantity-1'); + var appNg = angular.element(appDom); + var scope = appNg.scope(); + scope.config.datacenter = '""" + found_zone + """'; + scope.$apply(); + """ + + print_and_log("Executing select script: `%s`." % js_select_dhs) + driver.execute_script(js_select_dhs) + print_and_log("Selected canadian datacenter.") + + css_label_existing = "span.existing label" + css_button_login = "div.customer-existing form span.last.ec-button span button" + + id_input_login = "existing-customer-login" + id_input_pass = "existing-customer-password" + + # Check existing customer + button_existing = driver.find_element_by_css_selector(css_label_existing) + print_and_log("Button found: " + str(button_existing)) + button_existing.click() + print_and_log("Clicked on existing customer.") + + screenshot_step(driver, screen_prefix, 1) + driver.execute_script("arguments[0].scrollIntoView(true);", button_existing) + screenshot_step(driver, screen_prefix, 2) + # Locate login inputs + input_login = driver.find_element_by_id(id_input_login) + input_pass = driver.find_element_by_id(id_input_pass) + + # Connect with given credentials + input_login.send_keys(ovh_user) + input_pass.send_keys(ovh_pass) + print_and_log("Wrote username and password into inputs.") + driver.find_element_by_css_selector(css_button_login).click() + print_and_log("Clicked on login button.") + + screenshot_step(driver, screen_prefix, 3) + + # Wait for means of payment to load + css_payment_valid = "div.payment-means-choice div.payment-means-list form span.selected input.custom-radio.ng-valid" + WebDriverWait(driver, 20).until(EC.presence_of_element_located( + (By.CSS_SELECTOR, css_payment_valid) + )) + + # Check inputs to accept contract conditions + css_input_cgv = "div.dedicated-contracts input#contracts-validation" + css_input_custom = "div.dedicated-contracts input#customConractAccepted" + css_button_purchase = "div.dedicated-contracts button.centered" + driver.find_element_by_css_selector(css_input_cgv).click() + driver.find_element_by_css_selector(css_input_custom).click() + print_and_log("Checked confirmation inputs.") + + # uncommented after numerous tests + if not debug: + driver.find_element_by_css_selector(css_button_purchase).click() + print_and_log("Clicked on purchase button...") + + # Wait to realise what you've done + screenshot_step(driver, screen_prefix, 4) + time.sleep(30) + screenshot_step(driver, screen_prefix, 5) + if debug: + driver.close() + + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt index e69309e..18c5dd7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ -requests==2.8.1 -selenium==2.48.0 +requests>=2.21.0 +selenium>=3.141.0 +Click>=6.7 From 85d86da0e99cdf9a74dea99d6d0f84a2d9cb4802 Mon Sep 17 00:00:00 2001 From: Alain Rodriguez Date: Fri, 25 Jan 2019 03:48:45 +0000 Subject: [PATCH 2/5] Adding more options - payment frequency/number of servers --- buyKim.py | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/buyKim.py b/buyKim.py index 2a3c564..89a09b3 100755 --- a/buyKim.py +++ b/buyKim.py @@ -9,6 +9,7 @@ from selenium.webdriver.common.by import By from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.common.action_chains import ActionChains import click from utils import zoom_out, screenshot_step @@ -22,15 +23,25 @@ def print_and_log(message, level=logging.INFO, sep=' ', end='\n', flush=False): @click.command() @click.option('--timeout-conn', '-t', default=5, show_default=True, help='Maximum time in seconds to wait for webservice answer.') @click.option('--interval', '-i', default=7.5, show_default=True, help='Minimum interval in seconds between two requests') -@click.option('--product-family', '-f', default="Kimsufi", show_default=True, help='The family of servers (ie. "Kimsufi"/"So you Start")') +@click.option('--product-family', default="Kimsufi", show_default=True, help='The family of servers (ie. "Kimsufi"/"So you Start")') @click.option('--ref-product', '-p', default="1801sk12", show_default=True, help='Reference of the server (ie 1801sk12 for KS1, 1801sys29 for some soYouStart servers') @click.option('--ref-zones', '-z', default=["gra","rbx","lon","fra"], show_default=True, multiple=True, help='Data center short name(s) (ie "-z gra -z rbx")') +@click.option('--quantity', '-q', default=1, show_default=True, help='Number of servers to rent - 1 to 5 (Maximum)') +@click.option('--payment-frequency', '-f', default=1, show_default=True, help='Receive the bill every 1,3,6 or 12 month') @click.option('--ovh-user', prompt=True, hide_input=False) @click.option('--ovh-pass', prompt=True, hide_input=True) @click.option('--debug/--no-debug', default=False, help='Debug mode, disable by default. Add --debug flag to enable') -def main(timeout_conn, interval, product_family, ref_product, ref_zones, ovh_user, ovh_pass, debug): - MAX_REQ_TIMEOUT_READ = None +def main(timeout_conn, interval, product_family, ref_product, ref_zones, quantity, payment_frequency, ovh_user, ovh_pass, debug): + + # Check input is correct + possible_payment_frequency = [1,3,6,12] + if payment_frequency not in possible_payment_frequency: + raise IOError('Error: possible choices for the billing frequency are 1,3,6 or 12 months. You entered "--payment-frequency {}"'.format(payment_frequency)) + if quantity not in [1,2,3,4,5]: + raise IOError('Error: It is only possible to order 1 to 5 at once. You entered "--quantity {}"'.format(quantity)) + # define constants + MAX_REQ_TIMEOUT_READ = None url_availability = "https://ws.ovh.com/dedicated/r2/ws.dispatcher/getAvailability2" not_available_terms = ['unknown', 'unavailable'] @@ -136,13 +147,30 @@ def main(timeout_conn, interval, product_family, ref_product, ref_zones, ovh_use js_select_dhs = """var appDom = document.querySelector('#quantity-1'); var appNg = angular.element(appDom); var scope = appNg.scope(); - scope.config.datacenter = '""" + found_zone + """'; + scope.config.datacenter = '{}'; scope.$apply(); - """ + """.format(found_zone) - print_and_log("Executing select script: `%s`." % js_select_dhs) + print_and_log("""Executing select script: + {} + """.format(js_select_dhs)) driver.execute_script(js_select_dhs) - print_and_log("Selected canadian datacenter.") + print_and_log("Selected {} datacenter.".format(found_zone)) + + # Select quantity and payment options + selecor_quantity_line = driver.find_element_by_css_selector('tbody.configuration tr:nth-child(2)') + selector_quantity = driver.find_element_by_css_selector('tbody.configuration tr:nth-child(2) li:nth-child({})'.format(quantity)) + Hover = ActionChains(driver).move_to_element(selecor_quantity_line).move_to_element(selector_quantity) + Hover.click().perform() + print_and_log("Selected to rent {} servers.".format(quantity)) + + # possible_payment_frequency index + 1 gives the option number to pick + option = possible_payment_frequency.index(payment_frequency) + 1 + selecor_frequency_line = driver.find_element_by_css_selector('tbody.configuration tr:nth-child(3)') + selector_frequency = driver.find_element_by_css_selector('tbody.configuration tr:nth-child(3) li:nth-child({})'.format(option)) + Hover = ActionChains(driver).move_to_element(selecor_frequency_line).move_to_element(selector_frequency) + Hover.click().perform() + print_and_log("Selected to rent servers for {} months.".format(payment_frequency)) css_label_existing = "span.existing label" css_button_login = "div.customer-existing form span.last.ec-button span button" From 02e32b00291e3d8f7a428d1ad35813906410c345 Mon Sep 17 00:00:00 2001 From: Alain Rodriguez Date: Sat, 26 Jan 2019 05:24:07 +0000 Subject: [PATCH 3/5] Fix contract checkboxes and validation + read timeout --- buyKim.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/buyKim.py b/buyKim.py index 89a09b3..f68abe7 100755 --- a/buyKim.py +++ b/buyKim.py @@ -41,7 +41,7 @@ def main(timeout_conn, interval, product_family, ref_product, ref_zones, quantit raise IOError('Error: It is only possible to order 1 to 5 at once. You entered "--quantity {}"'.format(quantity)) # define constants - MAX_REQ_TIMEOUT_READ = None + MAX_REQ_TIMEOUT_READ = 60 url_availability = "https://ws.ovh.com/dedicated/r2/ws.dispatcher/getAvailability2" not_available_terms = ['unknown', 'unavailable'] @@ -207,15 +207,13 @@ def main(timeout_conn, interval, product_family, ref_product, ref_zones, quantit )) # Check inputs to accept contract conditions - css_input_cgv = "div.dedicated-contracts input#contracts-validation" - css_input_custom = "div.dedicated-contracts input#customConractAccepted" - css_button_purchase = "div.dedicated-contracts button.centered" - driver.find_element_by_css_selector(css_input_cgv).click() - driver.find_element_by_css_selector(css_input_custom).click() + driver.find_element_by_id("contracts-validation").click() + driver.find_element_by_id("customConractAccepted").click() print_and_log("Checked confirmation inputs.") # uncommented after numerous tests if not debug: + css_button_purchase = ".zone-content section:last button.centered" driver.find_element_by_css_selector(css_button_purchase).click() print_and_log("Clicked on purchase button...") @@ -223,8 +221,7 @@ def main(timeout_conn, interval, product_family, ref_product, ref_zones, quantit screenshot_step(driver, screen_prefix, 4) time.sleep(30) screenshot_step(driver, screen_prefix, 5) - if debug: - driver.close() + driver.close() if __name__ == '__main__': From b854c709dbaf66c2c2773bc3d7846e7a82c241e4 Mon Sep 17 00:00:00 2001 From: Alain Rodriguez Date: Sat, 26 Jan 2019 07:12:44 +0000 Subject: [PATCH 4/5] Fixed log path / screen names --- .gitignore | 1 + README.md | 2 ++ buyKim.py | 28 ++++++++++++++-------------- utils.py | 12 ++++++++---- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index d85b0b4..7980f89 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ __pycache__ examples *.log +.DS_Store diff --git a/README.md b/README.md index 79d3401..ac8d0e2 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ wasting your precious time waiting! # Usage +*Requires python 3* + - Install requirements: `pip install -r requirements.txt` - One of the dependency is `Selenium` that depends on drivers: https://github.com/SeleniumHQ/selenium/blob/master/py/docs/source/index.rst#user-content-drivers. diff --git a/buyKim.py b/buyKim.py index f68abe7..c278c80 100755 --- a/buyKim.py +++ b/buyKim.py @@ -45,23 +45,23 @@ def main(timeout_conn, interval, product_family, ref_product, ref_zones, quantit url_availability = "https://ws.ovh.com/dedicated/r2/ws.dispatcher/getAvailability2" not_available_terms = ['unknown', 'unavailable'] - time_run = datetime.now().strftime("%y-%m-%d %H-%M-%f") - - screenshot_dir = os.getenv("SCREENSHOT_DIR", os.path.abspath("screens")) - log_dir = os.getenv("LOG_DIR", os.getcwd()) + script_dir = os.path.dirname(os.path.realpath(__file__)) + # Get env var if any, or the script directory as a fallback + # Then in any case the "screens" folder to the path. + screenshot_dir = os.path.join(os.getenv("SCREENSHOT_DIR", script_dir), "screens") if not os.path.exists(screenshot_dir): os.makedirs(screenshot_dir) + print("Saving screenshots in {}".format(screenshot_dir)) + + # Logs configuration + log_dir = os.getenv("LOG_DIR", script_dir) if not os.path.exists(log_dir): os.makedirs(log_dir) log_filename = os.path.join(log_dir, "buyKim.log") - print("Log filename: {}".format(log_filename)) logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', filename=log_filename, level=logging.DEBUG) logging.getLogger("requests").setLevel(logging.WARNING) - print_and_log("Saving screenshots in {}".format(screenshot_dir)) - screen_prefix = screenshot_dir + time_run - available = False while not available: success = False @@ -184,9 +184,9 @@ def main(timeout_conn, interval, product_family, ref_product, ref_zones, quantit button_existing.click() print_and_log("Clicked on existing customer.") - screenshot_step(driver, screen_prefix, 1) + screenshot_step(driver, screenshot_dir, 1) driver.execute_script("arguments[0].scrollIntoView(true);", button_existing) - screenshot_step(driver, screen_prefix, 2) + screenshot_step(driver, screenshot_dir, 2) # Locate login inputs input_login = driver.find_element_by_id(id_input_login) input_pass = driver.find_element_by_id(id_input_pass) @@ -198,7 +198,7 @@ def main(timeout_conn, interval, product_family, ref_product, ref_zones, quantit driver.find_element_by_css_selector(css_button_login).click() print_and_log("Clicked on login button.") - screenshot_step(driver, screen_prefix, 3) + screenshot_step(driver, screenshot_dir, 3) # Wait for means of payment to load css_payment_valid = "div.payment-means-choice div.payment-means-list form span.selected input.custom-radio.ng-valid" @@ -213,14 +213,14 @@ def main(timeout_conn, interval, product_family, ref_product, ref_zones, quantit # uncommented after numerous tests if not debug: - css_button_purchase = ".zone-content section:last button.centered" + css_button_purchase = "div.zone-content div.dedicated-contracts button.centered" driver.find_element_by_css_selector(css_button_purchase).click() print_and_log("Clicked on purchase button...") # Wait to realise what you've done - screenshot_step(driver, screen_prefix, 4) + screenshot_step(driver, screenshot_dir, 4) time.sleep(30) - screenshot_step(driver, screen_prefix, 5) + screenshot_step(driver, screenshot_dir, 5) driver.close() diff --git a/utils.py b/utils.py index 15a5326..5c63d1d 100755 --- a/utils.py +++ b/utils.py @@ -1,3 +1,6 @@ +import os +from datetime import datetime + from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys @@ -11,8 +14,9 @@ def zoom_out(driver): html.send_keys(Keys.CONTROL, '-') -def screenshot_step(driver, screen_prefix, step_number): - step_filename = screen_prefix + " - step%d.png" % step_number - retval = driver.save_screenshot(step_filename) - print("Saving screenshot %d as %s..." % (step_number, step_filename), +def screenshot_step(driver, screen_path, step_number): + file_name = "{:%Y-%m-%d-%H-%M-%S}-step{}.png".format(datetime.now(), step_number) + screenshot_full_path = os.path.realpath(os.path.join(screen_path, file_name)) + retval = driver.save_screenshot(screenshot_full_path) + print("Saving screenshot {} as {}...".format(step_number, screenshot_full_path), "Success" if retval else "Error") From 45274f344a0c739db16eff912522eb311300e699 Mon Sep 17 00:00:00 2001 From: arodrime Date: Sat, 14 Nov 2020 02:50:56 +0100 Subject: [PATCH 5/5] Update November 2020 - popup / select payment --- buyKim.py | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/buyKim.py b/buyKim.py index c278c80..6185c48 100755 --- a/buyKim.py +++ b/buyKim.py @@ -28,8 +28,8 @@ def print_and_log(message, level=logging.INFO, sep=' ', end='\n', flush=False): @click.option('--ref-zones', '-z', default=["gra","rbx","lon","fra"], show_default=True, multiple=True, help='Data center short name(s) (ie "-z gra -z rbx")') @click.option('--quantity', '-q', default=1, show_default=True, help='Number of servers to rent - 1 to 5 (Maximum)') @click.option('--payment-frequency', '-f', default=1, show_default=True, help='Receive the bill every 1,3,6 or 12 month') -@click.option('--ovh-user', prompt=True, hide_input=False) -@click.option('--ovh-pass', prompt=True, hide_input=True) +@click.option('--ovh-user', default=lambda: os.environ.get('OVH_USERNAME', ''), show_default='OVH_USERNAME', prompt=True, hide_input=False) +@click.option('--ovh-pass', default=lambda: os.environ.get('OVH_PASSWORD', ''), show_default='OVH_PASSWORD', prompt=True, hide_input=True) @click.option('--debug/--no-debug', default=False, help='Debug mode, disable by default. Add --debug flag to enable') def main(timeout_conn, interval, product_family, ref_product, ref_zones, quantity, payment_frequency, ovh_user, ovh_pass, debug): @@ -42,7 +42,7 @@ def main(timeout_conn, interval, product_family, ref_product, ref_zones, quantit # define constants MAX_REQ_TIMEOUT_READ = 60 - url_availability = "https://ws.ovh.com/dedicated/r2/ws.dispatcher/getAvailability2" + url_availability = "https://www.kimsufi.com/fr/js/dedicatedAvailability/availability-data-ca.json" not_available_terms = ['unknown', 'unavailable'] script_dir = os.path.dirname(os.path.realpath(__file__)) @@ -51,14 +51,14 @@ def main(timeout_conn, interval, product_family, ref_product, ref_zones, quantit screenshot_dir = os.path.join(os.getenv("SCREENSHOT_DIR", script_dir), "screens") if not os.path.exists(screenshot_dir): os.makedirs(screenshot_dir) - print("Saving screenshots in {}".format(screenshot_dir)) + print_and_log("Saving screenshots in {}".format(screenshot_dir)) # Logs configuration log_dir = os.getenv("LOG_DIR", script_dir) if not os.path.exists(log_dir): os.makedirs(log_dir) log_filename = os.path.join(log_dir, "buyKim.log") - print("Log filename: {}".format(log_filename)) + print_and_log("Log filename: {}".format(log_filename)) logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', filename=log_filename, level=logging.DEBUG) logging.getLogger("requests").setLevel(logging.WARNING) @@ -74,9 +74,9 @@ def main(timeout_conn, interval, product_family, ref_product, ref_zones, quantit request_ws = requests.get(url_availability, timeout=(timeout_conn, MAX_REQ_TIMEOUT_READ)) data = request_ws.json() - if 'answer' in data: - if 'availability' in data['answer']: - available_servers = data['answer']['availability'] + if data: + if 'availability' in data: + available_servers = data['availability'] found_product = False for line in available_servers: if line['reference'] == ref_product: @@ -100,7 +100,6 @@ def main(timeout_conn, interval, product_family, ref_product, ref_zones, quantit if available: break - print_and_log("None of the zones was available ({})".format(", ".join(zone_avails))) if not found_zone: print_and_log("None of the data center was found for product {}.".format(', '.join(ref_zones), ref_product)) if not found_product: @@ -121,7 +120,7 @@ def main(timeout_conn, interval, product_family, ref_product, ref_zones, quantit time.sleep(1) msg_time = " (time: %f)" % time_elapsed if success: - print(msg_time) + print_and_log(msg_time) logging.log(logging.DEBUG, log_msg + msg_time) print_and_log("Exited availability loop, {} is available in {}!".format(ref_product, found_zone)) @@ -157,6 +156,13 @@ def main(timeout_conn, interval, product_family, ref_product, ref_zones, quantit driver.execute_script(js_select_dhs) print_and_log("Selected {} datacenter.".format(found_zone)) + # pass cookie popup... + try: + driver.find_element_by_id('header_tc_privacy_button').click() + print_and_log("Passed the cookie popup window.".format(quantity)) + except e: + pass + # Select quantity and payment options selecor_quantity_line = driver.find_element_by_css_selector('tbody.configuration tr:nth-child(2)') selector_quantity = driver.find_element_by_css_selector('tbody.configuration tr:nth-child(2) li:nth-child({})'.format(quantity)) @@ -201,26 +207,31 @@ def main(timeout_conn, interval, product_family, ref_product, ref_zones, quantit screenshot_step(driver, screenshot_dir, 3) # Wait for means of payment to load - css_payment_valid = "div.payment-means-choice div.payment-means-list form span.selected input.custom-radio.ng-valid" - WebDriverWait(driver, 20).until(EC.presence_of_element_located( - (By.CSS_SELECTOR, css_payment_valid) - )) + WebDriverWait(driver, 20).until( + EC.element_to_be_clickable((By.ID, "customConractAccepted")) + ) # Check inputs to accept contract conditions driver.find_element_by_id("contracts-validation").click() driver.find_element_by_id("customConractAccepted").click() print_and_log("Checked confirmation inputs.") + screenshot_step(driver, screenshot_dir, 4) + + selecor_payment_means = driver.find_element_by_css_selector('div.payment-means form span span.first:nth-child(1) label').click() + print_and_log("Checked confirmation inputs.") + screenshot_step(driver, screenshot_dir, 5) # uncommented after numerous tests if not debug: css_button_purchase = "div.zone-content div.dedicated-contracts button.centered" driver.find_element_by_css_selector(css_button_purchase).click() print_and_log("Clicked on purchase button...") + else: + print_and_log("Not clicking the purchase button because '--debug' flag was passed.") # Wait to realise what you've done - screenshot_step(driver, screenshot_dir, 4) - time.sleep(30) - screenshot_step(driver, screenshot_dir, 5) + time.sleep(15) + screenshot_step(driver, screenshot_dir, 6) driver.close()