|
#!/usr/bin/env python3 |
|
# Automatically download newly generated neural MtG cards for freestyle deck building |
|
# All credits to urzas.ai and respective authors |
|
# Please use with care! Do not create unnecessary load! |
|
|
|
import arrow |
|
import io |
|
import json |
|
import pathlib |
|
import PIL |
|
import time |
|
import sys |
|
|
|
import urllib.request |
|
import regex as re |
|
|
|
from selenium import webdriver |
|
from selenium.webdriver.chrome.options import Options |
|
from selenium.webdriver.common.by import By |
|
|
|
|
|
card_states = { |
|
"Penning Words...": 0, |
|
"Casting Shapes...": 1, |
|
"Forge New Spell": 2, |
|
} |
|
|
|
|
|
def wait_for_card(driver, sleep_time=1, max_retries=60): |
|
old_url = driver.current_url |
|
|
|
# Wait for button to show "Forge Another" |
|
btn_new_card = driver.find_elements(By.ID, "action-button")[0] |
|
card_info = None |
|
last_state = None |
|
retries = 0 |
|
while True: |
|
if retries > max_retries: |
|
print(" TIMEOUT :(") |
|
raise ConnectionAbortedError("Could not generate card in time.") |
|
txt = btn_new_card.text |
|
if txt in card_states.keys(): |
|
state = card_states[txt] |
|
elif txt == "Forge a Spell": |
|
btn_new_card.click() |
|
state = None |
|
else: |
|
raise ValueError(f"Didn't understand button's label: {txt}") |
|
# State transition |
|
if state != last_state: |
|
# Finished |
|
if state == 2: |
|
print(f" {retries * sleep_time}s") |
|
break |
|
else: |
|
if last_state != None: |
|
print(f" {retries * sleep_time}s") |
|
if state == 1: |
|
card_info = get_card_info(driver) |
|
print(card_info) |
|
print(txt, end="") |
|
sys.stdout.flush() |
|
elif state != None: |
|
print(".", end="") |
|
sys.stdout.flush() |
|
retries += 1 |
|
last_state = state |
|
time.sleep(sleep_time) |
|
|
|
# Wait for URL to update via automatic redirect |
|
print("Generating Image...", end="") |
|
sys.stdout.flush() |
|
retries = 0 |
|
while True: |
|
if retries > max_retries: |
|
# TODO FIXME NEW WEBSITE DOES NOT REDIRECT ANYMORE? |
|
print(" TIMEOUT :(") |
|
raise ConnectionAbortedError("Could not generate card in time.") |
|
break |
|
print(".", end="") |
|
sys.stdout.flush() |
|
time.sleep(sleep_time) |
|
try: |
|
new_url = driver.current_url |
|
except Exception as e: |
|
print(e) |
|
continue |
|
if new_url != old_url: |
|
break |
|
retries += 1 |
|
print(f" {retries * sleep_time}s") |
|
return card_info |
|
|
|
|
|
def convert_mana_cost_abbrev(element): |
|
html = element.get_attribute('innerHTML') |
|
html_replaced = re.sub(r'<abbr class="[^"]+ card-symbol-([A-Z0-9])" [^>]+>{T}</abbr>', r'[\1]', html) |
|
return html_replaced.replace(' ', '') |
|
|
|
|
|
def get_card_info(driver): |
|
get_css = lambda css: driver.find_element(By.CSS_SELECTOR, css) |
|
card_name = get_css('.card-frame > .frame-header > .name').text.strip() |
|
element_mana_cost = get_css('.card-frame > .frame-header > .mana-cost') |
|
card_mana_cost = convert_mana_cost_abbrev(element_mana_cost) |
|
card_type = get_css('.card-frame > .frame-type-line > .type').text |
|
element_description = get_css('.card-frame > .frame-text-box > .description') |
|
card_description = convert_mana_cost_abbrev(element_description).split('<br>') |
|
card_flavour_text = get_css('.card-frame > .frame-text-box > .flavour-text').text |
|
card_info = { |
|
'name': card_name.strip(), |
|
'mana_cost': card_mana_cost.strip(), |
|
'type': card_type.strip(), |
|
'description': [s.strip() for s in card_description], |
|
} |
|
return card_info |
|
|
|
|
|
def download_card(driver, img_dir): |
|
img_card = driver.find_elements(By.CSS_SELECTOR, ".card-side > img")[1] |
|
img_url = img_card.get_attribute('src') |
|
img_file = img_url.split('/')[-1].split('?')[0] |
|
img_path = f"{img_dir}/{img_file}" |
|
pathlib.Path(img_dir).mkdir(parents=True, exist_ok=True) |
|
try: |
|
urllib.request.urlretrieve(img_url, img_path) |
|
print(f"Saved card's image to {img_path}") |
|
except TimeoutError: |
|
print("Timeout while downloading card. :(") |
|
return img_file |
|
|
|
|
|
def initialize_driver(): |
|
# Initialize selenium driver |
|
options = webdriver.chrome.options.Options() |
|
options.headless = True |
|
options.add_argument("--window-size=1024,840") |
|
options.add_argument('--disable-browser-side-navigation') # Remedies hanging when getting current URL? |
|
driver = webdriver.Chrome(options=options) |
|
|
|
# Visit base URL |
|
url_start = "https://www.urzas.ai/" |
|
driver.get(url_start) |
|
return driver |
|
|
|
|
|
def save_card_info(card_info, info_file): |
|
pathlib.Path('/'.join(info_file.split('/')[:-1])).mkdir(parents=True, exist_ok=True) |
|
json_string = json.dumps(card_info) |
|
with open(info_file, 'w') as json_file: |
|
json_file.write(json_string) |
|
print(f"Saved card's info to {info_file}") |
|
|
|
|
|
def generate_and_save_card(driver): |
|
# Click button to generate card |
|
btn_new_card = driver.find_elements(By.ID, "action-button")[0] |
|
btn_new_card.click() |
|
try: |
|
card_info = wait_for_card(driver) |
|
except ConnectionAbortedError: |
|
print("Skipped because of timeout...") |
|
return False |
|
img_file = download_card(driver, 'cards/img') |
|
info_file = img_file.replace('.png', '.json') |
|
save_card_info(card_info, f'cards/info/{info_file}') |
|
return True |
|
|
|
|
|
if __name__ == '__main__': |
|
print("Initializing selenium driver...") |
|
n = 5 |
|
driver = initialize_driver() |
|
last_timestamp = arrow.utcnow() |
|
for i in range(n): |
|
print(f"Downloading card {i + 1}/{n}...") |
|
success = generate_and_save_card(driver) |
|
success_str = "Successfully" if success else "Failed to" |
|
timestamp = arrow.utcnow() |
|
print(f"{success_str} generate and download card within {(timestamp - last_timestamp).seconds}s") |
|
print() |
|
last_timestamp = timestamp |
|
driver.quit() |