#!/usr/bin/env python3 #encoding=utf-8 #執行方式:python chrome_tixcraft.py 或 python3 chrome_tixcraft.py #import jieba #from DrissionPage import ChromiumPage #import nodriver as uc import argparse import base64 import json import logging import os import platform import random import ssl import subprocess import sys import threading import time import warnings import webbrowser from datetime import datetime import chromedriver_autoinstaller_max import requests from selenium import webdriver from selenium.common.exceptions import (NoAlertPresentException, NoSuchWindowException, UnexpectedAlertPresentException, WebDriverException) from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import Select, WebDriverWait from urllib3.exceptions import InsecureRequestWarning import util from NonBrowser import NonBrowser try: import ddddocr except Exception as exc: print(exc) pass CONST_APP_VERSION = "MaxBot (2024.04.12)" CONST_MAXBOT_ANSWER_ONLINE_FILE = "MAXBOT_ONLINE_ANSWER.txt" CONST_MAXBOT_CONFIG_FILE = "settings.json" CONST_MAXBOT_EXTENSION_NAME = "Maxbotplus_1.0.0" CONST_MAXBOT_INT28_FILE = "MAXBOT_INT28_IDLE.txt" CONST_MAXBOT_LAST_URL_FILE = "MAXBOT_LAST_URL.txt" CONST_MAXBOT_QUESTION_FILE = "MAXBOT_QUESTION.txt" CONST_MAXBLOCK_EXTENSION_NAME = "Maxblockplus_1.0.0" CONST_MAXBLOCK_EXTENSION_FILTER =[ "*.doubleclick.net/*", "*.googlesyndication.com/*", "*.ssp.hinet.net/*", "*a.amnet.tw/*", "*adx.c.appier.net/*", "*cdn.cookielaw.org/*", "*cdnjs.cloudflare.com/ajax/libs/clipboard.js/*", "*clarity.ms/*", "*cloudfront.com/*", "*cms.analytics.yahoo.com/*", "*e2elog.fetnet.net/*", "*fundingchoicesmessages.google.com/*", "*ghtinc.com/*", "*google-analytics.com/*", "*googletagmanager.com/*", "*googletagservices.com/*", "*img.uniicreative.com/*", "*lndata.com/*", "*match.adsrvr.org/*", "*onead.onevision.com.tw/*", "*play.google.com/log?*", "*popin.cc/*", "*rollbar.com/*", "*sb.scorecardresearch.com/*", "*tagtoo.co/*", "*ticketmaster.sg/js/adblock*", "*ticketmaster.sg/js/adblock.js*", "*tixcraft.com/js/analytics.js*", "*tixcraft.com/js/common.js*", "*tixcraft.com/js/custom.js*", "*treasuredata.com/*", "*www.youtube.com/youtubei/v1/player/heartbeat*", ] CONST_CHROME_VERSION_NOT_MATCH_EN="Please download the WebDriver version to match your browser version." CONST_CHROME_VERSION_NOT_MATCH_TW="請下載與您瀏覽器相同版本的WebDriver版本,或更新您的瀏覽器版本。" CONST_CHROME_DRIVER_WEBSITE = 'https://chromedriver.chromium.org/' CONST_CITYLINE_SIGN_IN_URL = "https://www.cityline.com/Login.html?targetUrl=https%3A%2F%2Fwww.cityline.com%2FEvents.html" CONST_FAMI_SIGN_IN_URL = "https://www.famiticket.com.tw/Home/User/SignIn" CONST_HKTICKETING_SIGN_IN_URL = "https://premier.hkticketing.com/Secure/ShowLogin.aspx" CONST_KHAM_SIGN_IN_URL = "https://kham.com.tw/application/UTK13/UTK1306_.aspx" CONST_KKTIX_SIGN_IN_URL = "https://kktix.com/users/sign_in?back_to=%s" CONST_TICKET_SIGN_IN_URL = "https://ticket.com.tw/application/utk13/utk1306_.aspx" CONST_URBTIX_SIGN_IN_URL = "https://www.urbtix.hk/member-login" CONST_FROM_TOP_TO_BOTTOM = "from top to bottom" CONST_FROM_BOTTOM_TO_TOP = "from bottom to top" CONST_CENTER = "center" CONST_RANDOM = "random" CONT_STRING_1_SEATS_REMAINING = ['@1 seat(s) remaining','剩餘 1@','@1 席残り'] CONST_OCR_CAPTCH_IMAGE_SOURCE_NON_BROWSER = "NonBrowser" CONST_OCR_CAPTCH_IMAGE_SOURCE_CANVAS = "canvas" CONST_WEBDRIVER_TYPE_SELENIUM = "selenium" CONST_WEBDRIVER_TYPE_UC = "undetected_chromedriver" CONST_WEBDRIVER_TYPE_DP = "DrissionPage" CONST_WEBDRIVER_TYPE_NODRIVER = "nodriver" CONST_CHROME_FAMILY = ["chrome","edge","brave"] USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" CONST_PREFS_DICT = { "credentials_enable_service": False, "in_product_help.snoozed_feature.IPH_LiveCaption.is_dismissed": True, "in_product_help.snoozed_feature.IPH_LiveCaption.last_dismissed_by": 4, "media_router.show_cast_sessions_started_by_other_devices.enabled": False, "net.network_prediction_options": 3, "privacy_guide.viewed": True, "profile.default_content_setting_values.notifications": 2, "profile.default_content_setting_values.sound": 2, "profile.name": CONST_APP_VERSION, "profile.password_manager_enabled": False, "safebrowsing.enabled":False, "safebrowsing.enhanced":False, "sync.autofill_wallet_import_enabled_migrated":False, "translate":{"enabled": False}} warnings.simplefilter('ignore',InsecureRequestWarning) ssl._create_default_https_context = ssl._create_unverified_context logging.basicConfig() logger = logging.getLogger('logger') def get_config_dict(args): app_root = util.get_app_root() config_filepath = os.path.join(app_root, CONST_MAXBOT_CONFIG_FILE) # allow assign config by command line. if args.input: config_filepath = args.input config_dict = None if os.path.isfile(config_filepath): # start to overwrite config settings. with open(config_filepath) as json_data: config_dict = json.load(json_data) if args.headless is not None: config_dict["advanced"]["headless"] = util.t_or_f(args.headless) if args.homepage: config_dict["homepage"] = args.homepage if args.ticket_number: config_dict["ticket_number"] = args.ticket_number if args.browser: config_dict["browser"] = args.browser if args.tixcraft_sid: config_dict["advanced"]["tixcraft_sid"] = args.tixcraft_sid if args.ibonqware: config_dict["advanced"]["ibonqware"] = args.ibonqware if args.kktix_account: config_dict["advanced"]["kktix_account"] = args.kktix_account if args.kktix_password: config_dict["advanced"]["kktix_password_plaintext"] = args.kktix_password if args.proxy_server: config_dict["advanced"]["proxy_server_port"] = args.proxy_server if args.window_size: config_dict["advanced"]["window_size"] = args.window_size # special case for headless to enable away from keyboard mode. is_headless_enable_ocr = False if config_dict["advanced"]["headless"]: # for tixcraft headless. #print("If you are runnig headless mode on tixcraft, you need input your cookie SID.") if len(config_dict["advanced"]["tixcraft_sid"]) > 1: is_headless_enable_ocr = True if is_headless_enable_ocr: config_dict["ocr_captcha"]["enable"] = True config_dict["ocr_captcha"]["force_submit"] = True return config_dict def write_question_to_file(question_text): working_dir = os.path.dirname(os.path.realpath(__file__)) target_path = os.path.join(working_dir, CONST_MAXBOT_QUESTION_FILE) util.write_string_to_file(target_path, question_text) def write_last_url_to_file(url): working_dir = os.path.dirname(os.path.realpath(__file__)) target_path = os.path.join(working_dir, CONST_MAXBOT_LAST_URL_FILE) util.write_string_to_file(target_path, url) def read_last_url_from_file(): ret = "" with open(CONST_MAXBOT_LAST_URL_FILE, "r") as text_file: ret = text_file.readline() return ret def get_favoriate_extension_path(webdriver_path, config_dict): #print("webdriver_path:", webdriver_path) extension_list = [] extension_list.append(os.path.join(webdriver_path, CONST_MAXBOT_EXTENSION_NAME + ".crx")) extension_list.append(os.path.join(webdriver_path, CONST_MAXBLOCK_EXTENSION_NAME + ".crx")) return extension_list def get_chromedriver_path(webdriver_path): chromedriver_path = os.path.join(webdriver_path,"chromedriver") if platform.system().lower()=="windows": chromedriver_path = os.path.join(webdriver_path,"chromedriver.exe") return chromedriver_path def get_chrome_options(webdriver_path, config_dict): chrome_options = webdriver.ChromeOptions() if config_dict["browser"]=="edge": chrome_options = webdriver.EdgeOptions() if config_dict["browser"]=="safari": chrome_options = webdriver.SafariOptions() is_log_performace = False performace_site = ['ticketplus'] for site in performace_site: if site in config_dict["homepage"]: is_log_performace = True break if is_log_performace: if config_dict["browser"] in CONST_CHROME_FAMILY: chrome_options.set_capability("goog:loggingPrefs",{"performance": "ALL"}) # PS: this is crx version. extension_list = [] if config_dict["advanced"]["chrome_extension"]: extension_list = get_favoriate_extension_path(webdriver_path, config_dict) for ext in extension_list: if os.path.exists(ext): chrome_options.add_extension(ext) if config_dict["advanced"]["headless"]: #chrome_options.add_argument('--headless') chrome_options.add_argument('--headless=new') chrome_options.add_argument("--user-agent=%s" % (USER_AGENT)) chrome_options.add_argument("--disable-animations") chrome_options.add_argument("--disable-background-networking") chrome_options.add_argument("--disable-backgrounding-occluded-windows") chrome_options.add_argument("--disable-bookmark-reordering") chrome_options.add_argument("--disable-boot-animation") chrome_options.add_argument("--disable-breakpad") chrome_options.add_argument("--disable-canvas-aa") chrome_options.add_argument("--disable-client-side-phishing-detection") chrome_options.add_argument("--disable-cloud-import") chrome_options.add_argument("--disable-component-cloud-policy") chrome_options.add_argument("--disable-component-update") chrome_options.add_argument("--disable-composited-antialiasing") chrome_options.add_argument("--disable-default-apps") chrome_options.add_argument("--disable-dev-shm-usage") chrome_options.add_argument("--disable-device-discovery-notifications") chrome_options.add_argument("--disable-dinosaur-easter-egg") chrome_options.add_argument("--disable-domain-reliability") chrome_options.add_argument("--disable-features=IsolateOrigins,site-per-process,TranslateUI,PrivacySandboxSettings4") chrome_options.add_argument("--disable-infobars") chrome_options.add_argument("--disable-logging") chrome_options.add_argument("--disable-login-animations") chrome_options.add_argument("--disable-login-screen-apps") chrome_options.add_argument("--disable-notifications") chrome_options.add_argument("--disable-popup-blocking") chrome_options.add_argument("--disable-print-preview") chrome_options.add_argument("--disable-renderer-backgrounding") chrome_options.add_argument("--disable-session-crashed-bubble") chrome_options.add_argument("--disable-smooth-scrolling") chrome_options.add_argument("--disable-suggestions-ui") chrome_options.add_argument("--disable-sync") chrome_options.add_argument("--disable-translate") chrome_options.add_argument("--hide-crash-restore-bubble") chrome_options.add_argument("--lang=zh-TW") chrome_options.add_argument("--no-default-browser-check") chrome_options.add_argument("--no-first-run") chrome_options.add_argument("--no-pings") chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--no-service-autorun") chrome_options.add_argument("--password-store=basic") # for navigator.webdriver chrome_options.add_experimental_option("excludeSwitches", ['enable-automation']) # Deprecated chrome option is ignored: useAutomationExtension #chrome_options.add_experimental_option('useAutomationExtension', False) chrome_options.add_experimental_option("prefs", CONST_PREFS_DICT) if len(config_dict["advanced"]["proxy_server_port"]) > 2: chrome_options.add_argument('--proxy-server=%s' % config_dict["advanced"]["proxy_server_port"]) if config_dict["browser"]=="brave": brave_path = util.get_brave_bin_path() if os.path.exists(brave_path): chrome_options.binary_location = brave_path chrome_options.page_load_strategy = 'eager' #chrome_options.page_load_strategy = 'none' chrome_options.unhandled_prompt_behavior = "accept" return chrome_options def load_chromdriver_normal(config_dict, driver_type): show_debug_message = config_dict["advanced"]["verbose"] driver = None Root_Dir = util.get_app_root() webdriver_path = os.path.join(Root_Dir, "webdriver") chromedriver_path = get_chromedriver_path(webdriver_path) os.makedirs(webdriver_path, exist_ok=True) if not os.path.exists(chromedriver_path): print("WebDriver not exist, try to download to:", webdriver_path) chromedriver_autoinstaller_max.install(path=webdriver_path, make_version_dir=False) if not os.path.exists(chromedriver_path): print("Please download chromedriver and extract zip to webdriver folder from this url:") print("請下在面的網址下載與你chrome瀏覽器相同版本的chromedriver,解壓縮後放到webdriver目錄裡:") print(CONST_CHROME_DRIVER_WEBSITE) else: chrome_service = Service(chromedriver_path) chrome_options = get_chrome_options(webdriver_path, config_dict) try: driver = webdriver.Chrome(service=chrome_service, options=chrome_options) except WebDriverException as exc: error_message = str(exc) if show_debug_message: print(exc) left_part = error_message.split("Stacktrace:")[0] if "Stacktrace:" in error_message else None if "This version of ChromeDriver only supports Chrome version" in error_message: print(CONST_CHROME_VERSION_NOT_MATCH_EN) print(CONST_CHROME_VERSION_NOT_MATCH_TW) # remove exist chromedriver, download again. try: print("Deleting exist and download ChromeDriver again.") os.unlink(chromedriver_path) except Exception as exc2: print(exc2) pass chromedriver_autoinstaller_max.install(path=webdriver_path, make_version_dir=False) chrome_service = Service(chromedriver_path) try: chrome_options = get_chrome_options(webdriver_path, config_dict) driver = webdriver.Chrome(service=chrome_service, options=chrome_options) except WebDriverException as exc2: print("Selenium 4.11.0 Release with Chrome For Testing Browser.") try: chrome_options = get_chrome_options(webdriver_path, config_dict) driver = webdriver.Chrome(service=Service(), options=chrome_options) except WebDriverException as exc3: print(exc3) pass return driver def get_uc_options(uc, config_dict, webdriver_path): options = uc.ChromeOptions() options.page_load_strategy = 'eager' #options.page_load_strategy = 'none' options.unhandled_prompt_behavior = "accept" #print("strategy", options.page_load_strategy) is_log_performace = False performace_site = ['ticketplus'] for site in performace_site: if site in config_dict["homepage"]: is_log_performace = True break if is_log_performace: options.set_capability("goog:loggingPrefs",{"performance": "ALL"}) load_extension_path = "" extension_list = [] if config_dict["advanced"]["chrome_extension"]: extension_list = get_favoriate_extension_path(webdriver_path, config_dict) for ext in extension_list: ext = ext.replace('.crx','') if os.path.exists(ext): # sync config. if CONST_MAXBOT_EXTENSION_NAME in ext: util.dump_settings_to_maxbot_plus_extension(ext, config_dict, CONST_MAXBOT_CONFIG_FILE) if CONST_MAXBLOCK_EXTENSION_NAME in ext: util.dump_settings_to_maxblock_plus_extension(ext, config_dict, CONST_MAXBOT_CONFIG_FILE, CONST_MAXBLOCK_EXTENSION_FILTER) load_extension_path += ("," + os.path.abspath(ext)) #print("load_extension_path:", load_extension_path) if len(load_extension_path) > 0: #print('load-extension:', load_extension_path[1:]) options.add_argument('--load-extension=' + load_extension_path[1:]) if config_dict["advanced"]["headless"]: #options.add_argument('--headless') options.add_argument('--headless=new') options.add_argument("--user-agent=%s" % (USER_AGENT)) options.add_argument("--disable-animations") options.add_argument("--disable-background-networking") options.add_argument("--disable-backgrounding-occluded-windows") options.add_argument("--disable-bookmark-reordering") options.add_argument("--disable-boot-animation") options.add_argument("--disable-breakpad") options.add_argument("--disable-canvas-aa") options.add_argument("--disable-client-side-phishing-detection") options.add_argument("--disable-cloud-import") options.add_argument("--disable-component-cloud-policy") options.add_argument("--disable-component-update") options.add_argument("--disable-composited-antialiasing") options.add_argument("--disable-default-apps") options.add_argument("--disable-dev-shm-usage") options.add_argument("--disable-device-discovery-notifications") options.add_argument("--disable-dinosaur-easter-egg") options.add_argument("--disable-domain-reliability") options.add_argument("--disable-features=IsolateOrigins,site-per-process,TranslateUI,PrivacySandboxSettings4") options.add_argument("--disable-infobars") options.add_argument("--disable-logging") options.add_argument("--disable-login-animations") options.add_argument("--disable-login-screen-apps") options.add_argument("--disable-notifications") options.add_argument("--disable-popup-blocking") options.add_argument("--disable-print-preview") options.add_argument("--disable-renderer-backgrounding") options.add_argument("--disable-session-crashed-bubble") options.add_argument("--disable-smooth-scrolling") options.add_argument("--disable-suggestions-ui") options.add_argument("--disable-sync") options.add_argument("--disable-translate") options.add_argument("--hide-crash-restore-bubble") options.add_argument("--lang=zh-TW") options.add_argument("--no-default-browser-check") options.add_argument("--no-first-run") options.add_argument("--no-pings") options.add_argument("--no-sandbox") options.add_argument("--no-service-autorun") options.add_argument("--password-store=basic") options.add_experimental_option("prefs", CONST_PREFS_DICT) if len(config_dict["advanced"]["proxy_server_port"]) > 2: options.add_argument('--proxy-server=%s' % config_dict["advanced"]["proxy_server_port"]) if config_dict["browser"]=="brave": brave_path = util.get_brave_bin_path() if os.path.exists(brave_path): options.binary_location = brave_path return options def load_chromdriver_uc(config_dict): import undetected_chromedriver as uc show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True Root_Dir = util.get_app_root() webdriver_path = os.path.join(Root_Dir, "webdriver") chromedriver_path = get_chromedriver_path(webdriver_path) if not os.path.exists(webdriver_path): os.mkdir(webdriver_path) if not os.path.exists(chromedriver_path): print("ChromeDriver not exist, try to download to:", webdriver_path) try: chromedriver_autoinstaller_max.install(path=webdriver_path, make_version_dir=False) if not os.path.exists(chromedriver_path): print("check installed chrome version fail, download last known good version.") chromedriver_autoinstaller_max.install(path=webdriver_path, make_version_dir=False, detect_installed_version=False) except Exception as exc: print(exc) else: print("ChromeDriver exist:", chromedriver_path) driver = None if os.path.exists(chromedriver_path): # use chromedriver_autodownload instead of uc auto download. is_cache_exist = util.clean_uc_exe_cache() fail_1 = False lanch_uc_with_path = True if "macos" in platform.platform().lower(): if "arm64" in platform.platform().lower(): lanch_uc_with_path = False if lanch_uc_with_path: try: options = get_uc_options(uc, config_dict, webdriver_path) driver = uc.Chrome(driver_executable_path=chromedriver_path, options=options, headless=config_dict["advanced"]["headless"]) except Exception as exc: print(exc) error_message = str(exc) left_part = None if "Stacktrace:" in error_message: left_part = error_message.split("Stacktrace:")[0] print(left_part) if "This version of ChromeDriver only supports Chrome version" in error_message: print(CONST_CHROME_VERSION_NOT_MATCH_EN) print(CONST_CHROME_VERSION_NOT_MATCH_TW) fail_1 = True else: fail_1 = True fail_2 = False if fail_1: try: options = get_uc_options(uc, config_dict, webdriver_path) driver = uc.Chrome(options=options) except Exception as exc: print(exc) fail_2 = True if fail_2: # remove exist chromedriver, download again. try: print("Deleting exist and download ChromeDriver again.") os.unlink(chromedriver_path) except Exception as exc2: print(exc2) pass try: chromedriver_autoinstaller_max.install(path=webdriver_path, make_version_dir=False) options = get_uc_options(uc, config_dict, webdriver_path) driver = uc.Chrome(driver_executable_path=chromedriver_path, options=options) except Exception as exc2: print(exc2) pass else: print("WebDriver not found at path:", chromedriver_path) if driver is None: print('WebDriver object is still None..., try download by uc.') try: options = get_uc_options(uc, config_dict, webdriver_path) driver = uc.Chrome(options=options) except Exception as exc: print(exc) error_message = str(exc) left_part = None if "Stacktrace:" in error_message: left_part = error_message.split("Stacktrace:")[0] print(left_part) if "This version of ChromeDriver only supports Chrome version" in error_message: print(CONST_CHROME_VERSION_NOT_MATCH_EN) print(CONST_CHROME_VERSION_NOT_MATCH_TW) pass if driver is None: print("create web drive object by undetected_chromedriver fail!") if os.path.exists(chromedriver_path): print("Unable to use undetected_chromedriver, ") print("try to use local chromedriver to launch chrome browser.") driver_type = "selenium" driver = load_chromdriver_normal(config_dict, driver_type) else: print("建議您自行下載 ChromeDriver 到 webdriver 的資料夾下") print("you need manually download ChromeDriver to webdriver folder.") return driver def close_browser_tabs(driver): if not driver is None: try: window_handles_count = len(driver.window_handles) if window_handles_count > 1: driver.switch_to.window(driver.window_handles[1]) driver.close() driver.switch_to.window(driver.window_handles[0]) except Exception as excSwithFail: pass def get_driver_by_config(config_dict): driver = None # read config. homepage = config_dict["homepage"] # output config: print("maxbot app version:", CONST_APP_VERSION) print("python version:", platform.python_version()) print("platform:", platform.platform()) print("homepage:", homepage) print("browser:", config_dict["browser"]) #print("headless:", config_dict["advanced"]["headless"]) #print("ticket_number:", str(config_dict["ticket_number"])) #print(config_dict["tixcraft"]) #print("==[advanced config]==") if config_dict["advanced"]["verbose"]: print(config_dict["advanced"]) print("webdriver_type:", config_dict["webdriver_type"]) # entry point if homepage is None: homepage = "" Root_Dir = util.get_app_root() webdriver_path = os.path.join(Root_Dir, "webdriver") #print("platform.system().lower():", platform.system().lower()) if config_dict["browser"] in ["chrome","brave"]: # method 6: Selenium Stealth if config_dict["webdriver_type"] == CONST_WEBDRIVER_TYPE_SELENIUM: driver = load_chromdriver_normal(config_dict, config_dict["webdriver_type"]) if config_dict["webdriver_type"] == CONST_WEBDRIVER_TYPE_UC: # method 5: uc # multiprocessing not work bug. if platform.system().lower()=="windows": if hasattr(sys, 'frozen'): from multiprocessing import freeze_support freeze_support() driver = load_chromdriver_uc(config_dict) if config_dict["webdriver_type"] == CONST_WEBDRIVER_TYPE_DP: #driver = ChromiumPage() pass if config_dict["browser"] == "firefox": # default os is linux/mac # download url: https://github.com/mozilla/geckodriver/releases chromedriver_path = os.path.join(webdriver_path,"geckodriver") if platform.system().lower()=="windows": chromedriver_path = os.path.join(webdriver_path,"geckodriver.exe") if "macos" in platform.platform().lower(): if "arm64" in platform.platform().lower(): chromedriver_path = os.path.join(webdriver_path,"geckodriver_arm") webdriver_service = Service(chromedriver_path) driver = None try: from selenium.webdriver.firefox.options import Options options = Options() if config_dict["advanced"]["headless"]: options.add_argument('--headless') #options.add_argument('--headless=new') if platform.system().lower()=="windows": binary_path = "C:\\Program Files\\Mozilla Firefox\\firefox.exe" if not os.path.exists(binary_path): binary_path = os.path.expanduser('~') + "\\AppData\\Local\\Mozilla Firefox\\firefox.exe" if not os.path.exists(binary_path): binary_path = "C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe" if not os.path.exists(binary_path): binary_path = "D:\\Program Files\\Mozilla Firefox\\firefox.exe" options.binary_location = binary_path driver = webdriver.Firefox(service=webdriver_service, options=options) except Exception as exc: error_message = str(exc) left_part = None if "Stacktrace:" in error_message: left_part = error_message.split("Stacktrace:")[0] print(left_part) else: print(exc) if config_dict["browser"] == "edge": # default os is linux/mac # download url: https://developer.microsoft.com/zh-tw/microsoft-edge/tools/webdriver/ chromedriver_path = os.path.join(webdriver_path,"msedgedriver") if platform.system().lower()=="windows": chromedriver_path = os.path.join(webdriver_path,"msedgedriver.exe") webdriver_service = Service(chromedriver_path) chrome_options = get_chrome_options(webdriver_path, config_dict) driver = None try: driver = webdriver.Edge(service=webdriver_service, options=chrome_options) except Exception as exc: error_message = str(exc) #print(error_message) left_part = None if "Stacktrace:" in error_message: left_part = error_message.split("Stacktrace:")[0] print(left_part) if config_dict["browser"] == "safari": driver = None try: driver = webdriver.Safari() except Exception as exc: error_message = str(exc) #print(error_message) left_part = None if "Stacktrace:" in error_message: left_part = error_message.split("Stacktrace:")[0] print(left_part) if driver is None: print("create web driver object fail @_@;") else: try: NETWORK_BLOCKED_URLS = [ '*.clarity.ms/*', '*.cloudfront.com/*', '*.doubleclick.net/*', '*.lndata.com/*', '*.rollbar.com/*', '*.twitter.com/i/*', '*/adblock.js', '*/google_ad_block.js', '*cityline.com/js/others.min.js', '*anymind360.com/*', '*cdn.cookielaw.org/*', '*e2elog.fetnet.net*', '*fundingchoicesmessages.google.com/*', '*google-analytics.*', '*googlesyndication.*', '*googletagmanager.*', '*googletagservices.*', '*img.uniicreative.com/*', '*platform.twitter.com/*', '*play.google.com/*', '*player.youku.*', '*syndication.twitter.com/*', '*youtube.com/*', ] if config_dict["advanced"]["hide_some_image"]: NETWORK_BLOCKED_URLS.append('*.woff') NETWORK_BLOCKED_URLS.append('*.woff2') NETWORK_BLOCKED_URLS.append('*.ttf') NETWORK_BLOCKED_URLS.append('*.otf') NETWORK_BLOCKED_URLS.append('*fonts.googleapis.com/earlyaccess/*') NETWORK_BLOCKED_URLS.append('*/ajax/libs/font-awesome/*') NETWORK_BLOCKED_URLS.append('*.ico') NETWORK_BLOCKED_URLS.append('*ticketimg2.azureedge.net/image/ActivityImage/*') NETWORK_BLOCKED_URLS.append('*static.tixcraft.com/images/activity/*') NETWORK_BLOCKED_URLS.append('*static.ticketmaster.sg/images/activity/*') NETWORK_BLOCKED_URLS.append('*static.ticketmaster.com/images/activity/*') NETWORK_BLOCKED_URLS.append('*ticketimg2.azureedge.net/image/ActivityImage/ActivityImage_*') NETWORK_BLOCKED_URLS.append('*.azureedge.net/QWARE_TICKET//images/*') NETWORK_BLOCKED_URLS.append('*static.ticketplus.com.tw/event/*') #NETWORK_BLOCKED_URLS.append('https://kktix.cc/change_locale?locale=*') NETWORK_BLOCKED_URLS.append('https://t.kfs.io/assets/logo_*.png') NETWORK_BLOCKED_URLS.append('https://t.kfs.io/assets/icon-*.png') NETWORK_BLOCKED_URLS.append('https://t.kfs.io/upload_images/*.jpg') if config_dict["advanced"]["block_facebook_network"]: NETWORK_BLOCKED_URLS.append('*facebook.com/*') NETWORK_BLOCKED_URLS.append('*.fbcdn.net/*') # Chrome DevTools Protocal if config_dict["browser"] in CONST_CHROME_FAMILY: driver.execute_cdp_cmd('Network.setBlockedURLs', {"urls": NETWORK_BLOCKED_URLS}) driver.execute_cdp_cmd('Network.enable', {}) if 'kktix.c' in homepage: if len(config_dict["advanced"]["kktix_account"])>0: # for like human. try: driver.get(homepage) time.sleep(5) except Exception as e: pass if not 'https://kktix.com/users/sign_in?' in homepage: homepage = CONST_KKTIX_SIGN_IN_URL % (homepage) if 'famiticket.com' in homepage: if len(config_dict["advanced"]["fami_account"])>0: homepage = CONST_FAMI_SIGN_IN_URL if 'kham.com' in homepage: if len(config_dict["advanced"]["kham_account"])>0: homepage = CONST_KHAM_SIGN_IN_URL if 'ticket.com.tw' in homepage: if len(config_dict["advanced"]["ticket_account"])>0: homepage = CONST_TICKET_SIGN_IN_URL if 'urbtix.hk' in homepage: if len(config_dict["advanced"]["urbtix_account"])>0: homepage = CONST_URBTIX_SIGN_IN_URL if 'cityline.com' in homepage: if len(config_dict["advanced"]["cityline_account"])>0: homepage = CONST_CITYLINE_SIGN_IN_URL if 'hkticketing.com' in homepage: if len(config_dict["advanced"]["hkticketing_account"])>0: homepage = CONST_HKTICKETING_SIGN_IN_URL if 'ticketplus.com.tw' in homepage: if len(config_dict["advanced"]["ticketplus_account"]) > 1: homepage = "https://ticketplus.com.tw/" print("goto url:", homepage) driver.get(homepage) time.sleep(3.0) tixcraft_family = False if 'tixcraft.com' in homepage: tixcraft_family = True if 'indievox.com' in homepage: tixcraft_family = True if 'ticketmaster.' in homepage: tixcraft_family = True if tixcraft_family: tixcraft_sid = config_dict["advanced"]["tixcraft_sid"] if len(tixcraft_sid) > 1: driver.delete_cookie("SID") driver.add_cookie({"name":"SID", "value": tixcraft_sid, "path" : "/", "secure":True}) if 'ibon.com' in homepage: ibonqware = config_dict["advanced"]["ibonqware"] if len(ibonqware) > 1: driver.delete_cookie("ibonqware") driver.add_cookie({"name":"ibonqware", "value": ibonqware, "domain" : "ibon.com.tw", "secure":True}) except WebDriverException as exce2: print('oh no not again, WebDriverException') print('WebDriverException:', exce2) except Exception as exce1: print('get URL Exception:', exce1) pass return driver def force_press_button_iframe(driver, f, select_by, select_query, force_submit=True): if not f: # ensure we are on main content frame try: driver.switch_to.default_content() except Exception as exc: pass else: try: driver.switch_to.frame(f) except Exception as exc: pass is_clicked = press_button(driver, select_by, select_query, force_submit) if f: # switch back to main content, otherwise we will get StaleElementReferenceException try: driver.switch_to.default_content() except Exception as exc: pass return is_clicked def remove_attribute_tag_by_selector(driver, select_query, class_name, more_script = ""): element_script = "eachItem.removeAttribute('"+ class_name +"');" javascript_tag_by_selector(driver, select_query, element_script, more_script = more_script) def remove_class_tag_by_selector(driver, select_query, class_name, more_script = ""): element_script = "eachItem.classList.remove('"+ class_name +"');" javascript_tag_by_selector(driver, select_query, element_script, more_script = more_script) def hide_tag_by_selector(driver, select_query, more_script = ""): element_script = "eachItem.style='display:none;';" javascript_tag_by_selector(driver, select_query, element_script, more_script = more_script) def clean_tag_by_selector(driver, select_query, more_script = ""): element_script = "eachItem.outerHTML='';" javascript_tag_by_selector(driver, select_query, element_script, more_script = more_script) # PS: selector query string must without single quota. def javascript_tag_by_selector(driver, select_query, element_script, more_script = ""): try: driver.set_script_timeout(1) js = """var selectSoldoutItems = document.querySelectorAll('%s'); selectSoldoutItems.forEach((eachItem) => {%s}); %s""" % (select_query, element_script, more_script) #print("javascript:", js) driver.execute_script(js) ret = True except Exception as exc: #print(exc) pass def press_button(driver, select_by, select_query, force_submit=True): ret = False next_step_button = None try: next_step_button = driver.find_element(select_by ,select_query) if not next_step_button is None: if next_step_button.is_enabled(): next_step_button.click() ret = True except Exception as exc: #print("find %s clickable Exception:" % (select_query)) #print(exc) pass if force_submit: if not next_step_button is None: is_visible = False try: if next_step_button.is_enabled(): is_visible = True except Exception as exc: pass if is_visible: try: driver.set_script_timeout(1) driver.execute_script("arguments[0].click();", next_step_button) ret = True except Exception as exc: pass return ret # close some div on home url. def tixcraft_home_close_window(driver): accept_all_cookies_btn = None try: accept_all_cookies_btn = driver.find_element(By.CSS_SELECTOR, '#onetrust-accept-btn-handler') if accept_all_cookies_btn: accept_all_cookies_btn.click() except Exception as exc: #print(exc) pass # from detail to game def tixcraft_redirect(driver, url): ret = False game_name = "" url_split = url.split("/") if len(url_split) >= 6: game_name = url_split[5] if len(game_name) > 0: if "/activity/detail/%s" % (game_name,) in url: entry_url = url.replace("/activity/detail/","/activity/game/") print("redirec to new url:", entry_url) try: driver.get(entry_url) ret = True except Exception as exec1: pass return ret def tixcraft_date_auto_select(driver, url, config_dict, domain_name): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True # read config. auto_select_mode = config_dict["date_auto_select"]["mode"] date_keyword = config_dict["date_auto_select"]["date_keyword"].strip() pass_date_is_sold_out_enable = config_dict["tixcraft"]["pass_date_is_sold_out"] auto_reload_coming_soon_page_enable = config_dict["tixcraft"]["auto_reload_coming_soon_page"] # PS: for big events, check sold out text maybe not helpful, due to database is too busy. sold_out_text_list = ["選購一空","已售完","No tickets available","Sold out","空席なし","完売した"] # PS: "Start ordering" for indievox.com. find_ticket_text_list = ['立即訂購','Find tickets', 'Start ordering','お申込みへ進む'] game_name = "" if "/activity/game/" in url: url_split = url.split("/") if len(url_split) >= 6: game_name = url_split[5] if show_debug_message: print('get date game_name:', game_name) print("date_auto_select_mode:", auto_select_mode) print("date_keyword:", date_keyword) check_game_detail = False # choose date if "/activity/game/%s" % (game_name,) in url: if show_debug_message: if len(date_keyword) == 0: print("date keyword is empty.") else: print("date keyword:", date_keyword) check_game_detail = True area_list = None if check_game_detail: if show_debug_message: print("start to query #gameList info.") my_css_selector = '#gameList > table > tbody > tr' try: area_list = driver.find_elements(By.CSS_SELECTOR, my_css_selector) if not area_list is None: if len(area_list)==0: # only headless mode detected now. if config_dict["advanced"]["headless"]: html_body = driver.page_source if not html_body is None: if len(html_body) > 0: html_text = util.remove_html_tags(html_body) bot_detected_string_list = ['Your Session Has Been Suspended' , 'Something about your browsing behavior or network made us think you were a bot' , 'Your browser hit a snag and we need to make sure you' ] for each_string in bot_detected_string_list: print(html_text) break except Exception as exc: print("find #gameList fail") is_coming_soon = False coming_soon_condictions_list_en = [' day(s)', ' hrs.',' min',' sec',' till sale starts!','0',':','/'] coming_soon_condictions_list_tw = ['開賣','剩餘',' 天',' 小時',' 分鐘',' 秒','0',':','/','20'] coming_soon_condictions_list_ja = ['発売開始', ' 日', ' 時間',' 分',' 秒','0',':','/','20'] coming_soon_condictions_list = coming_soon_condictions_list_en html_lang="en-US" try: html_body = driver.page_source if not html_body is None: if len(html_body) > 0: if ' 0: formated_area_list = [] for row in area_list: row_text = "" row_html = "" try: #row_text = row.text row_html = row.get_attribute('innerHTML') row_text = util.remove_html_tags(row_html) except Exception as exc: if show_debug_message: print(exc) # error, exit loop break if len(row_text) > 0: if util.reset_row_text_if_match_keyword_exclude(config_dict, row_text): row_text = "" if len(row_text) > 0: # check is coming soon events in list. is_match_all_coming_soon_condiction = True for condiction_string in coming_soon_condictions_list: if not condiction_string in row_text: is_match_all_coming_soon_condiction = False break if is_match_all_coming_soon_condiction: if show_debug_message: print("match coming soon condiction at row:", row_text) is_coming_soon = True if is_coming_soon: if auto_reload_coming_soon_page_enable: break row_is_enabled=False for text_item in find_ticket_text_list: if text_item in row_text: row_is_enabled = True break # check sold out text. if row_is_enabled: if pass_date_is_sold_out_enable: for sold_out_item in sold_out_text_list: row_text_right_part = row_text[(len(sold_out_item)+5)*-1:] if show_debug_message: #print("check right part text:", row_text_right_part) pass if sold_out_item in row_text_right_part: row_is_enabled = False if show_debug_message: print("match sold out text: %s, skip this row." % (sold_out_item)) break if row_is_enabled: formated_area_list.append(row) if show_debug_message: print("formated_area_list count:", len(formated_area_list)) if len(date_keyword) == 0: matched_blocks = formated_area_list else: # match keyword. if show_debug_message: print("start to match formated keyword:", date_keyword) matched_blocks = util.get_matched_blocks_by_keyword(config_dict, auto_select_mode, date_keyword, formated_area_list) if show_debug_message: if not matched_blocks is None: print("after match keyword, found count:", len(matched_blocks)) else: print("not found date-time-position") pass else: print("date date-time-position is None") pass target_area = util.get_target_item_from_matched_list(matched_blocks, auto_select_mode) is_date_clicked = False if not target_area is None: if show_debug_message: print("target_area got, start to press button.") is_date_clicked = press_button(target_area, By.CSS_SELECTOR,'button') if not is_date_clicked: if show_debug_message: print("press button fail, try to click hyperlink.") if "tixcraft" in domain_name: try: data_href = target_area.get_attribute("data-href") if not data_href is None: print("goto url:", data_href) driver.get(data_href) else: if show_debug_message: print("data-href not ready") # delay 200ms to click. #driver.set_script_timeout(0.3) #js="""setTimeout(function(){arguments[0].click()},200);""" #driver.execute_script(js, target_area) except Exception as exc: pass # for: ticketmaster.sg is_date_clicked = press_button(target_area, By.CSS_SELECTOR,'a') # [PS]: current reload condition only when if auto_reload_coming_soon_page_enable: if is_coming_soon: if show_debug_message: print("match is_coming_soon, start to reload page.") # case 2: match one row is coming soon. try: driver.refresh() except Exception as exc: pass else: if not is_date_clicked: if not formated_area_list is None: if len(formated_area_list) == 0: print('start to refresh page.') try: driver.refresh() time.sleep(0.3) except Exception as exc: pass if config_dict["advanced"]["auto_reload_page_interval"] > 0: time.sleep(config_dict["advanced"]["auto_reload_page_interval"]) return is_date_clicked def ticketmaster_date_auto_select(driver, url, config_dict, domain_name): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True # read config. auto_select_mode = config_dict["date_auto_select"]["mode"] date_keyword = config_dict["date_auto_select"]["date_keyword"].strip() # TODO: implement this feature. date_keyword_and = "" pass_date_is_sold_out_enable = config_dict["tixcraft"]["pass_date_is_sold_out"] auto_reload_coming_soon_page_enable = config_dict["tixcraft"]["auto_reload_coming_soon_page"] # PS: for big events, check sold out text maybe not helpful, due to database is too busy. sold_out_text_list = ["選購一空","已售完","No tickets available","Sold out","空席なし","完売した"] find_ticket_text_list = ['See Tickets'] area_list = None try: area_list = driver.find_elements(By.CSS_SELECTOR, '#list-view > div > div.event-listing > div.accordion-wrapper > div') except Exception as exc: print("find #gameList fail") matched_blocks = None formated_area_list = None if not area_list is None: area_list_count = len(area_list) if show_debug_message: print("date_list_count:", area_list_count) if area_list_count > 0: formated_area_list = [] for row in area_list: row_text = "" row_html = "" try: #row_text = row.text row_html = row.get_attribute('innerHTML') row_text = util.remove_html_tags(row_html) except Exception as exc: if show_debug_message: print(exc) # error, exit loop break if len(row_text) > 0: row_is_enabled=False # must contains 'See Tickets' for text_item in find_ticket_text_list: if text_item in row_text: row_is_enabled = True break # check sold out text. if row_is_enabled: if pass_date_is_sold_out_enable: for sold_out_item in sold_out_text_list: if sold_out_item in row_text: row_is_enabled = False if show_debug_message: print("match sold out text: %s, skip this row." % (sold_out_item)) break if row_is_enabled: formated_area_list.append(row) if show_debug_message: print("formated_area_list count:", len(formated_area_list)) if len(date_keyword) == 0: matched_blocks = formated_area_list else: # match keyword. date_keyword = util.format_keyword_string(date_keyword) if show_debug_message: print("start to match formated keyword:", date_keyword) matched_blocks = util.get_matched_blocks_by_keyword(config_dict, auto_select_mode, date_keyword, formated_area_list) if show_debug_message: if not matched_blocks is None: print("after match keyword, found count:", len(matched_blocks)) else: print("not found date-time-position") pass else: print("date date-time-position is None") pass target_area = util.get_target_item_from_matched_list(matched_blocks, auto_select_mode) is_date_clicked = False if not target_area is None: is_date_clicked = press_button(target_area, By.CSS_SELECTOR,'a') if is_date_clicked: try: window_handles_count = len(driver.window_handles) if window_handles_count > 1: driver.switch_to.window(driver.window_handles[0]) driver.close() driver.switch_to.window(driver.window_handles[0]) time.sleep(0.2) except Exception as excSwithFail: pass # [PS]: current reload condition only when if auto_reload_coming_soon_page_enable: if not is_date_clicked: if not formated_area_list is None: if len(formated_area_list) == 0: print('start to refresh page.') try: driver.refresh() time.sleep(0.3) except Exception as exc: pass return is_date_clicked # PURPOSE: get target area list. # RETURN: # is_need_refresh # matched_blocks # PS: matched_blocks will be None, if length equals zero. def get_tixcraft_target_area(el, config_dict, area_keyword_item): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True # read config. area_auto_select_mode = config_dict["area_auto_select"]["mode"] is_need_refresh = False matched_blocks = None area_list = None area_list_count = 0 if not el is None: try: area_list = el.find_elements(By.TAG_NAME, 'a') except Exception as exc: #print("find area list a tag fail") pass if not area_list is None: area_list_count = len(area_list) if area_list_count == 0: print("area list is empty, do refresh!") is_need_refresh = True else: print("area list is None, do refresh!") is_need_refresh = True if area_list_count > 0: matched_blocks = [] for row in area_list: row_text = "" row_html = "" try: #row_text = row.text row_html = row.get_attribute('innerHTML') row_text = util.remove_html_tags(row_html) except Exception as exc: if show_debug_message: print(exc) # error, exit loop break if len(row_text) > 0: if util.reset_row_text_if_match_keyword_exclude(config_dict, row_text): row_text = "" if len(row_text) > 0: # clean stop word. row_text = util.format_keyword_string(row_text) is_append_this_row = False if len(area_keyword_item) > 0: # must match keyword. is_append_this_row = True area_keyword_array = area_keyword_item.split(' ') for area_keyword in area_keyword_array: area_keyword = util.format_keyword_string(area_keyword) if not area_keyword in row_text: is_append_this_row = False break else: # without keyword. is_append_this_row = True if is_append_this_row: if config_dict["ticket_number"] > 1: area_item_font_el = None try: #print('try to find font tag at row:', row_text) area_item_font_el = row.find_element(By.TAG_NAME, 'font') if not area_item_font_el is None: font_el_text = area_item_font_el.text if font_el_text is None: font_el_text = "" font_el_text = "@%s@" % (font_el_text) if show_debug_message: print('font tag text:', font_el_text) pass for check_item in CONT_STRING_1_SEATS_REMAINING: if check_item in font_el_text: if show_debug_message: print("match pass 1 seats remaining 1 full text:", row_text) print("match pass 1 seats remaining 2 font text:", font_el_text) is_append_this_row = False else: #print("row withou font tag.") pass except Exception as exc: #print("find font text in a tag fail:", exc) pass if show_debug_message: print("is_append_this_row:", is_append_this_row) if is_append_this_row: matched_blocks.append(row) if area_auto_select_mode == CONST_FROM_TOP_TO_BOTTOM: #print("only need first item, break area list loop.") break if len(matched_blocks) == 0: matched_blocks = None is_need_refresh = True return is_need_refresh, matched_blocks def get_ticketmaster_target_area(config_dict, area_keyword_item, zone_info): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True # read config. area_auto_select_mode = config_dict["area_auto_select"]["mode"] is_need_refresh = False matched_blocks = None area_list_count = len(zone_info) if show_debug_message: print("area_list_count:", area_list_count) if area_list_count > 0: matched_blocks = [] for row in zone_info: row_is_enabled=False if zone_info[row]["areaStatus"] != "UNAVAILABLE": row_is_enabled = True if zone_info[row]["areaStatus"] == "SINGLE SEATS": row_is_enabled = True if config_dict["ticket_number"] > 1: row_is_enabled = False row_text = "" if row_is_enabled: try: row_text = zone_info[row]["groupName"] row_text += " " + zone_info[row]["description"] if "price" in zone_info[row]: row_text += " " + zone_info[row]["price"][0]["ticketPrice"] except Exception as exc: if show_debug_message: print("get text fail:", exc, zone_info[row]) pass if row_text is None: row_text = "" if len(row_text) > 0: if util.reset_row_text_if_match_keyword_exclude(config_dict, row_text): row_text = "" if len(row_text) > 0: # clean stop word. row_text = util.format_keyword_string(row_text) if show_debug_message: #print("formated row_text:", row_text) pass is_append_this_row = False if len(area_keyword_item) > 0: # must match keyword. is_append_this_row = True area_keyword_array = area_keyword_item.split(' ') for area_keyword in area_keyword_array: area_keyword = util.format_keyword_string(area_keyword) if not area_keyword in row_text: is_append_this_row = False break else: # without keyword. is_append_this_row = True if show_debug_message: #print("is_append_this_row:", is_append_this_row) pass if is_append_this_row: matched_blocks.append(row) if area_auto_select_mode == CONST_FROM_TOP_TO_BOTTOM: #print("only need first item, break area list loop.") break if len(matched_blocks) == 0: matched_blocks = None is_need_refresh = True return is_need_refresh, matched_blocks # PS: auto refresh condition 1: no keyword + no hyperlink. # PS: auto refresh condition 2: with keyword + no hyperlink. def tixcraft_area_auto_select(driver, url, config_dict): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True # read config. area_keyword = config_dict["area_auto_select"]["area_keyword"].strip() auto_select_mode = config_dict["area_auto_select"]["mode"] ticket_number = config_dict["ticket_number"] if show_debug_message: print("area_keyword:", area_keyword) el = None try: el = driver.find_element(By.CSS_SELECTOR, '.zone') except Exception as exc: print("find .zone fail, do nothing.") if not el is None: is_need_refresh = False matched_blocks = None if len(area_keyword) > 0: area_keyword_array = [] try: area_keyword_array = json.loads("["+ area_keyword +"]") except Exception as exc: area_keyword_array = [] for area_keyword_item in area_keyword_array: is_need_refresh, matched_blocks = get_tixcraft_target_area(el, config_dict, area_keyword_item) if not is_need_refresh: break else: print("is_need_refresh for keyword:", area_keyword_item) else: # empty keyword, match all. is_need_refresh, matched_blocks = get_tixcraft_target_area(el, config_dict, "") target_area = util.get_target_item_from_matched_list(matched_blocks, auto_select_mode) if not target_area is None: try: target_area.click() except Exception as exc: print("click area a link fail, start to retry...") try: driver.execute_script("arguments[0].click();", target_area) except Exception as exc: print("click area a link fail, after reftry still fail.") print(exc) pass # auto refresh for area list page. if is_need_refresh: try: driver.refresh() except Exception as exc: pass if config_dict["advanced"]["auto_reload_page_interval"] > 0: time.sleep(config_dict["advanced"]["auto_reload_page_interval"]) def ticketmaster_area_auto_select(driver, config_dict, zone_info): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True # read config. area_keyword = config_dict["area_auto_select"]["area_keyword"].strip() if show_debug_message: print("area_keyword:", area_keyword) is_need_refresh = False matched_blocks = None if len(area_keyword) > 0: area_keyword_array = [] try: area_keyword_array = json.loads("["+ area_keyword +"]") except Exception as exc: area_keyword_array = [] for area_keyword_item in area_keyword_array: is_need_refresh, matched_blocks = get_ticketmaster_target_area(config_dict, area_keyword_item, zone_info) if not is_need_refresh: break else: print("is_need_refresh for keyword:", area_keyword_item) else: # empty keyword, match all. is_need_refresh, matched_blocks = get_ticketmaster_target_area(config_dict, "", zone_info) auto_select_mode = config_dict["area_auto_select"]["mode"] target_area = util.get_target_item_from_matched_list(matched_blocks, auto_select_mode) if not target_area is None: try: #print("area text:", target_area.text) click_area_javascript = 'areaTicket("%s", "map");' % target_area if show_debug_message: #print("click_area_javascript:", click_area_javascript) pass driver.execute_script(click_area_javascript) except Exception as exc: if show_debug_message: print(exc) pass # auto refresh for area list page. if is_need_refresh: try: driver.refresh() except Exception as exc: pass if config_dict["advanced"]["auto_reload_page_interval"] > 0: time.sleep(config_dict["advanced"]["auto_reload_page_interval"]) def ticket_number_select_fill(driver, select_obj, ticket_number): is_ticket_number_assigned = False if not select_obj is None: try: # target ticket number select_obj.select_by_visible_text(ticket_number) #select.select_by_value(ticket_number) #select.select_by_index(int(ticket_number)) is_ticket_number_assigned = True except Exception as exc: print("select_by_visible_text ticket_number fail") print(exc) try: # target ticket number select_obj.select_by_visible_text(ticket_number) #select.select_by_value(ticket_number) #select.select_by_index(int(ticket_number)) is_ticket_number_assigned = True except Exception as exc: print("select_by_visible_text ticket_number fail...2") print(exc) # try buy one ticket try: select_obj.select_by_visible_text("1") #select.select_by_value("1") #select.select_by_index(int(ticket_number)) is_ticket_number_assigned = True except Exception as exc: print("select_by_visible_text 1 fail") pass # Plan B. # if not is_ticket_number_assigned: if False: if not select is None: try: # target ticket number #select.select_by_visible_text(ticket_number) print("assign ticker number by jQuery:",ticket_number) driver.execute_script("$(\"input[type='select']\").val(\""+ ticket_number +"\");") is_ticket_number_assigned = True except Exception as exc: print("jQuery select_by_visible_text ticket_number fail (after click.)") print(exc) return is_ticket_number_assigned def get_text_by_selector(driver, my_css_selector, attribute='innerHTML'): div_element = None try: div_element = driver.find_element(By.CSS_SELECTOR, my_css_selector) except Exception as exc: #print("find element fail") pass row_text = "" if not div_element is None: try: if attribute=='innerText': row_html = div_element.get_attribute('innerHTML') row_text = util.remove_html_tags(row_html) else: row_text = div_element.get_attribute(attribute) except Exception as exc: print("get text fail:", my_css_selector) return row_text def fill_common_verify_form(driver, config_dict, inferred_answer_string, fail_list, input_text_css, next_step_button_css, submit_by_enter, check_input_interval): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True form_input_list = [] try: form_input_list = driver.find_elements(By.CSS_SELECTOR, input_text_css) except Exception as exc: if show_debug_message: print("find verify code input textbox fail") pass if form_input_list is None: form_input_list = [] form_input_count = len(form_input_list) if show_debug_message: print("input textbox count:", form_input_count) is_do_press_next_button = False form_input_1 = None form_input_2 = None if form_input_count > 0: form_input_1 = form_input_list[0] if form_input_count > 1: form_input_2 = form_input_list[1] is_multi_question_mode = False answer_list = util.get_answer_list_from_user_guess_string(config_dict, CONST_MAXBOT_ANSWER_ONLINE_FILE) if form_input_count == 1: is_do_press_next_button = True else: if form_input_count == 2: if not form_input_2 is None: if len(answer_list) >= 2: if(len(answer_list[0]) > 0): if(len(answer_list[1]) > 0): is_multi_question_mode = True inputed_value_1 = None if not form_input_1 is None: try: inputed_value_1 = form_input_1.get_attribute('value') except Exception as exc: if show_debug_message: print("get_attribute of verify code fail") pass if inputed_value_1 is None: inputed_value_1 = "" inputed_value_2 = None if not form_input_2 is None: try: inputed_value_2 = form_input_2.get_attribute('value') except Exception as exc: if show_debug_message: print("get_attribute of verify code fail") pass if inputed_value_2 is None: inputed_value_2 = "" is_answer_sent = False if not is_multi_question_mode: if not form_input_1 is None: if len(inferred_answer_string) > 0: if inputed_value_1 != inferred_answer_string: try: # PS: sometime may send key twice... form_input_1.clear() form_input_1.send_keys(inferred_answer_string) except Exception as exc: if show_debug_message: print(exc) pass is_button_clicked = False try: if is_do_press_next_button: if submit_by_enter: form_input_1.send_keys(Keys.ENTER) is_button_clicked = True else: if len(next_step_button_css) > 0: is_button_clicked = press_button(driver, By.CSS_SELECTOR, next_step_button_css) except Exception as exc: if show_debug_message: print(exc) pass if is_button_clicked: is_answer_sent = True fail_list.append(inferred_answer_string) if show_debug_message: print("sent password by bot:", inferred_answer_string, " at #", len(fail_list)) if is_answer_sent: for i in range(3): time.sleep(0.1) alert_ret = check_pop_alert(driver) if alert_ret: if show_debug_message: print("press accept button at time #", i+1) break else: # no answer to fill. if len(inputed_value_1)==0: try: # solution 1: js. driver.execute_script("if(!(document.activeElement === arguments[0])){arguments[0].focus();}", form_input_1) # solution 2: selenium. #form_input_1.click() time.sleep(check_input_interval) except Exception as exc: pass else: # multi question mode. try: if inputed_value_1 != answer_list[0]: form_input_1.clear() form_input_1.send_keys(answer_list[0]) if inputed_value_2 != answer_list[1]: form_input_2.clear() form_input_2.send_keys(answer_list[1]) is_button_clicked = False form_input_2.send_keys(Keys.ENTER) if len(next_step_button_css) > 0: is_button_clicked = press_button(driver, By.CSS_SELECTOR, next_step_button_css) if is_button_clicked: is_answer_sent = True fail_list.append(answer_list[0]) fail_list.append(answer_list[1]) if show_debug_message: print("sent password by bot:", inferred_answer_string, " at #", len(fail_list)) except Exception as exc: pass return is_answer_sent, fail_list def ticketmaster_promo(driver, config_dict, fail_list): question_selector = '#promoBox' return tixcraft_input_check_code(driver, config_dict, fail_list, question_selector) def tixcraft_verify(driver, config_dict, fail_list): question_selector = '.zone-verify' return tixcraft_input_check_code(driver, config_dict, fail_list, question_selector) def tixcraft_input_check_code(driver, config_dict, fail_list, question_selector): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True answer_list = [] question_text = get_text_by_selector(driver, question_selector, 'innerText') if len(question_text) > 0: write_question_to_file(question_text) answer_list = util.get_answer_list_from_user_guess_string(config_dict, CONST_MAXBOT_ANSWER_ONLINE_FILE) if len(answer_list)==0: if config_dict["advanced"]["auto_guess_options"]: answer_list = util.guess_tixcraft_question(driver, question_text) inferred_answer_string = "" for answer_item in answer_list: if not answer_item in fail_list: inferred_answer_string = answer_item break if show_debug_message: print("inferred_answer_string:", inferred_answer_string) print("answer_list:", answer_list) # PS: auto-focus() when empty inferred_answer_string with empty inputed text value. input_text_css = "input[name='checkCode']" next_step_button_css = "" submit_by_enter = True check_input_interval = 0.2 is_answer_sent, fail_list = fill_common_verify_form(driver, config_dict, inferred_answer_string, fail_list, input_text_css, next_step_button_css, submit_by_enter, check_input_interval) return fail_list def tixcraft_change_captcha(driver,url): try: driver.execute_script(f"document.querySelector('.verify-img').children[0].setAttribute('src','{url}');") except Exception as exc: print("edit captcha element fail") def tixcraft_toast(driver, message): toast_element = None try: my_css_selector = ".remark-word" toast_element = driver.find_element(By.CSS_SELECTOR, my_css_selector) if not toast_element is None: driver.execute_script("arguments[0].innerHTML='%s';" % message, toast_element) except Exception as exc: print("find toast element fail") def tixcraft_keyin_captcha_code(driver, answer = "", auto_submit = False): is_verifyCode_editing = False is_form_sumbited = False # manually keyin verify code. # start to input verify code. form_verifyCode = None try: form_verifyCode = driver.find_element(By.CSS_SELECTOR, '#TicketForm_verifyCode') except Exception as exc: print("find form_verifyCode fail") if not form_verifyCode is None: is_visible = False try: if form_verifyCode.is_enabled(): is_visible = True except Exception as exc: pass inputed_value = None try: inputed_value = form_verifyCode.get_attribute('value') except Exception as exc: print("find verify code fail") pass if inputed_value is None: inputed_value = "" is_visible = False if is_visible: try: form_verifyCode.click() is_verifyCode_editing = True except Exception as exc: print("click form_verifyCode fail, trying to use javascript.") # plan B try: driver.execute_script("document.getElementById(\"TicketForm_verifyCode\").focus();") is_verifyCode_editing = True except Exception as exc: #print("click form_verifyCode fail.") pass if len(answer) > 0: #print("start to fill answer.") try: form_verifyCode.clear() form_verifyCode.send_keys(answer) if auto_submit: form_verifyCode.send_keys(Keys.ENTER) is_verifyCode_editing = False is_form_sumbited = True else: driver.execute_script("document.getElementById(\"TicketForm_verifyCode\").select();") tixcraft_toast(driver, "※ 按 Enter 如果答案是: " + answer) except Exception as exc: print("send_keys ocr answer fail.") return is_verifyCode_editing, is_form_sumbited def tixcraft_reload_captcha(driver, domain_name): # manually keyin verify code. # start to input verify code. ret = False form_captcha = None try: image_id = 'TicketForm_verifyCode-image' if 'indievox.com' in domain_name: image_id = 'TicketForm_verifyCode-image' form_captcha = driver.find_element(By.CSS_SELECTOR, "#" + image_id) if not form_captcha is None: form_captcha.click() ret = True except Exception as exc: print("find form_captcha fail") return ret def tixcraft_get_ocr_answer(driver, ocr, ocr_captcha_image_source, Captcha_Browser, domain_name): show_debug_message = True # debug. show_debug_message = False # online ocr_answer = None if not ocr is None: img_base64 = None if ocr_captcha_image_source == CONST_OCR_CAPTCH_IMAGE_SOURCE_NON_BROWSER: if not Captcha_Browser is None: img_base64 = base64.b64decode(Captcha_Browser.request_captcha()) if ocr_captcha_image_source == CONST_OCR_CAPTCH_IMAGE_SOURCE_CANVAS: image_id = 'TicketForm_verifyCode-image' image_element = None try: my_css_selector = "#" + image_id image_element = driver.find_elements(By.CSS_SELECTOR, my_css_selector) except Exception as exc: pass if not image_element is None: if 'indievox.com' in domain_name: #image_id = 'TicketForm_verifyCode-image' pass try: driver.set_script_timeout(1) form_verifyCode_base64 = driver.execute_async_script(""" var canvas = document.createElement('canvas'); var context = canvas.getContext('2d'); var img = document.getElementById('%s'); if(img!=null) { canvas.height = img.naturalHeight; canvas.width = img.naturalWidth; context.drawImage(img, 0, 0); callback = arguments[arguments.length - 1]; callback(canvas.toDataURL()); } """ % (image_id)) if not form_verifyCode_base64 is None: img_base64 = base64.b64decode(form_verifyCode_base64.split(',')[1]) if img_base64 is None: if not Captcha_Browser is None: print("canvas get image fail, use plan_b: NonBrowser") img_base64 = base64.b64decode(Captcha_Browser.request_captcha()) except Exception as exc: if show_debug_message: print("canvas exception:", str(exc)) pass if not img_base64 is None: try: ocr_answer = ocr.classification(img_base64) except Exception as exc: pass return ocr_answer #PS: credit to LinShihJhang's share def tixcraft_auto_ocr(driver, ocr, away_from_keyboard_enable, previous_answer, Captcha_Browser, ocr_captcha_image_source, domain_name): show_debug_message = True # debug. show_debug_message = False # online is_need_redo_ocr = False is_form_sumbited = False is_input_box_exist = False if not ocr is None: form_verifyCode = None try: form_verifyCode = driver.find_element(By.CSS_SELECTOR, '#TicketForm_verifyCode') is_input_box_exist = True except Exception as exc: pass else: print("ddddocr component is not able to use, you may running in arm environment.") if is_input_box_exist: if show_debug_message: print("away_from_keyboard_enable:", away_from_keyboard_enable) print("previous_answer:", previous_answer) print("ocr_captcha_image_source:", ocr_captcha_image_source) ocr_start_time = time.time() ocr_answer = tixcraft_get_ocr_answer(driver, ocr, ocr_captcha_image_source, Captcha_Browser, domain_name) ocr_done_time = time.time() ocr_elapsed_time = ocr_done_time - ocr_start_time if show_debug_message: print("ocr elapsed time:", "{:.3f}".format(ocr_elapsed_time)) if ocr_answer is None: if away_from_keyboard_enable: # page is not ready, retry again. # PS: usually occur in async script get captcha image. is_need_redo_ocr = True time.sleep(0.1) else: tixcraft_keyin_captcha_code(driver) else: ocr_answer = ocr_answer.strip() if show_debug_message: print("ocr_answer:", ocr_answer) if len(ocr_answer)==4: who_care_var, is_form_sumbited = tixcraft_keyin_captcha_code(driver, answer = ocr_answer, auto_submit = away_from_keyboard_enable) else: if not away_from_keyboard_enable: tixcraft_keyin_captcha_code(driver) tixcraft_toast(driver, "※ OCR辨識失敗Q_Q,驗證碼請手動輸入...") else: is_need_redo_ocr = True if previous_answer != ocr_answer: previous_answer = ocr_answer if show_debug_message: print("click captcha again.") if True: # selenium solution. tixcraft_reload_captcha(driver, domain_name) if ocr_captcha_image_source == CONST_OCR_CAPTCH_IMAGE_SOURCE_CANVAS: time.sleep(0.1) else: # Non_Browser solution. if not Captcha_Browser is None: new_captcha_url = Captcha_Browser.request_refresh_captcha() #取得新的CAPTCHA if new_captcha_url != "": tixcraft_change_captcha(driver, new_captcha_url) #更改CAPTCHA圖 else: print("input box not exist, quit ocr...") return is_need_redo_ocr, previous_answer, is_form_sumbited def tixcraft_ticket_main_agree(driver, config_dict): is_finish_checkbox_click = False for i in range(3): is_finish_checkbox_click = check_checkbox(driver, By.CSS_SELECTOR, '#TicketForm_agree') if is_finish_checkbox_click: break return is_finish_checkbox_click def get_tixcraft_ticket_select_by_keyword(driver, config_dict, area_keyword_item): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True area_auto_select_mode = config_dict["area_auto_select"]["mode"] is_need_refresh = False matched_blocks = None area_list = None area_list_count = 0 try: my_css_selector = "table#ticketPriceList > tbody > tr" area_list = driver.find_elements(By.CSS_SELECTOR, my_css_selector) except Exception as exc: #print("find area list a tag fail") pass if not area_list is None: area_list_count = len(area_list) if area_list_count == 0: print("area list is empty, do refresh!") is_need_refresh = True else: print("area list is None, do refresh!") is_need_refresh = True if area_list_count > 0: matched_blocks = [] for row in area_list: row_text = "" row_html = "" try: #row_text = row.text row_html = row.get_attribute('innerHTML') row_text = util.remove_html_tags(row_html) except Exception as exc: if show_debug_message: print(exc) # error, exit loop break if len(row_text) > 0: if util.reset_row_text_if_match_keyword_exclude(config_dict, row_text): row_text = "" if len(row_text) > 0: # clean stop word. row_text = util.format_keyword_string(row_text) is_append_this_row = False if len(area_keyword_item) > 0: # must match keyword. is_append_this_row = True area_keyword_array = area_keyword_item.split(' ') for area_keyword in area_keyword_array: area_keyword = util.format_keyword_string(area_keyword) if not area_keyword in row_text: is_append_this_row = False break else: # without keyword. is_append_this_row = True if show_debug_message: print("is_append_this_row:", is_append_this_row, row_text) if is_append_this_row: matched_blocks.append(row) if area_auto_select_mode == CONST_FROM_TOP_TO_BOTTOM: #print("only need first item, break area list loop.") break if len(matched_blocks) == 0: matched_blocks = None is_need_refresh = True return is_need_refresh, matched_blocks def get_tixcraft_ticket_select(driver, config_dict): area_keyword = config_dict["area_auto_select"]["area_keyword"].strip() form_select = None matched_blocks = None if len(area_keyword) > 0: area_keyword_array = [] try: area_keyword_array = json.loads("["+ area_keyword +"]") except Exception as exc: area_keyword_array = [] for area_keyword_item in area_keyword_array: is_need_refresh, matched_blocks = get_tixcraft_ticket_select_by_keyword(driver, config_dict, area_keyword_item) if not is_need_refresh: break else: print("is_need_refresh for keyword:", area_keyword_item) else: # empty keyword, match all. is_need_refresh, matched_blocks = get_tixcraft_target_area(driver, config_dict, "") auto_select_mode = config_dict["area_auto_select"]["mode"] target_area = util.get_target_item_from_matched_list(matched_blocks, auto_select_mode) if not target_area is None: try: form_select = target_area.find_element(By.TAG_NAME, 'select') except Exception as exc: #print("find area list a tag fail") form_select = None pass return form_select def tixcraft_assign_ticket_number(driver, config_dict): is_ticket_number_assigned = False # allow agree not enable to assign ticket number. form_select_list = None try: form_select_list = driver.find_elements(By.CSS_SELECTOR, '.mobile-select') except Exception as exc: print("find select fail") pass form_select = None form_select_count = 0 if not form_select_list is None: form_select_count = len(form_select_list) if form_select_count >= 1: form_select = form_select_list[0] # multi select box if form_select_count > 1: if config_dict["area_auto_select"]["enable"]: # for tixcraft form_select_temp = get_tixcraft_ticket_select(driver, config_dict) if not form_select_temp is None: form_select = form_select_temp # for ticketmaster if form_select is None: try: form_select = driver.find_element(By.CSS_SELECTOR, 'td > select.form-select') except Exception as exc: print("find form-select fail") pass select_obj = None if not form_select is None: try: select_obj = Select(form_select) except Exception as exc: pass if not select_obj is None: row_text = None try: selected_option = select_obj.first_selected_option row_text = selected_option.text except Exception as exc: pass if not row_text is None: if len(row_text) > 0: if row_text != "0": if row_text.isnumeric(): # ticket assign. is_ticket_number_assigned = True return is_ticket_number_assigned, select_obj def tixcraft_ticket_main(driver, config_dict, ocr, Captcha_Browser, domain_name): is_agree_at_webdriver = False if not config_dict["browser"] in CONST_CHROME_FAMILY: is_agree_at_webdriver = True else: if not config_dict["advanced"]["chrome_extension"]: is_agree_at_webdriver = True if is_agree_at_webdriver: # use extension instead of selenium. # checkbox javascrit code at chrome extension. tixcraft_ticket_main_agree(driver, config_dict) is_ticket_number_assigned = False # PS: some events on tixcraft have multi 0: tmp_ticket_count = tmp_array[0].strip() if tmp_ticket_count.isdigit(): ticket_count = int(tmp_ticket_count) if show_debug_message: print("found ticket 剩:", tmp_ticket_count) # for ja. if ' danger' in row_html and '残り' in row_text and '枚' in row_text: tmp_array = row_html.split('残り') tmp_array = tmp_array[1].split('枚') if len(tmp_array) > 0: tmp_ticket_count = tmp_array[0].strip() if tmp_ticket_count.isdigit(): ticket_count = int(tmp_ticket_count) if show_debug_message: print("found ticket 残り:", tmp_ticket_count) # for en. if ' danger' in row_html and ' Left ' in row_html: tmp_array = row_html.split(' Left ') tmp_array = tmp_array[0].split('>') if len(tmp_array) > 0: tmp_ticket_count = tmp_array[len(tmp_array)-1].strip() if tmp_ticket_count.isdigit(): if show_debug_message: print("found ticket left:", tmp_ticket_count) ticket_count = int(tmp_ticket_count) if ticket_count < ticket_number: # skip this row, due to no ticket remaining. if show_debug_message: print("found ticket left:", tmp_ticket_count, ",but target ticket:", ticket_number) row_text = "" if len(row_text) > 0: # check ticket input textbox. ticket_price_input = None try: ticket_price_input = row.find_element(By.CSS_SELECTOR, "input[type='text']") except Exception as exc: pass if not ticket_price_input is None: current_ticket_number = "" is_visible = False try: current_ticket_number = str(ticket_price_input.get_attribute('value')).strip() is_visible = ticket_price_input.is_enabled() except Exception as exc: pass if len(current_ticket_number) > 0: if current_ticket_number != "0": is_ticket_number_assigned = True if is_ticket_number_assigned: # no need to travel break if is_visible: is_match_area = False match_area_code = 0 if len(kktix_area_keyword_1) == 0: # keyword #1, empty, direct add to list. is_match_area = True match_area_code = 1 else: # MUST match keyword #1. if kktix_area_keyword_1 in row_text: #print('match keyword#1') # because of logic between keywords is AND! if len(kktix_area_keyword_1_and) == 0: #print('keyword#2 is empty, directly match.') # keyword #2 is empty, direct append. is_match_area = True match_area_code = 2 else: if kktix_area_keyword_1_and in row_text: #print('match keyword#2') is_match_area = True match_area_code = 3 else: #print('not match keyword#2') pass else: #print('not match keyword#1') pass if show_debug_message: print("is_match_area:", is_match_area) print("match_area_code:", match_area_code) if is_match_area: areas.append(ticket_price_input) # from top to bottom, match first to break. if kktix_area_auto_select_mode == CONST_FROM_TOP_TO_BOTTOM: break if not is_dom_ready: # not sure to break or continue..., maybe break better. break else: if show_debug_message: print("no any price list found.") pass return is_dom_ready, is_ticket_number_assigned, areas def kktix_assign_ticket_number(driver, config_dict, kktix_area_keyword): show_debug_message = config_dict["advanced"]["verbose"] ticket_number_str = str(config_dict["ticket_number"]) auto_select_mode = config_dict["area_auto_select"]["mode"] is_ticket_number_assigned = False matched_blocks = None is_dom_ready = True is_dom_ready, is_ticket_number_assigned, matched_blocks = kktix_travel_price_list(driver, config_dict, auto_select_mode, kktix_area_keyword) target_area = None is_need_refresh = False if is_dom_ready: if not is_ticket_number_assigned: target_area = util.get_target_item_from_matched_list(matched_blocks, auto_select_mode) if not matched_blocks is None: if len(matched_blocks) == 0: is_need_refresh = True if show_debug_message: print("matched_blocks is empty, is_need_refresh") if not target_area is None: current_ticket_number = "" if show_debug_message: print("try to get input box value.") try: current_ticket_number = str(target_area.get_attribute('value')).strip() except Exception as exc: pass if len(current_ticket_number) > 0: if current_ticket_number == "0": try: print("asssign ticket number:%s" % ticket_number_str) target_area.clear() target_area.send_keys(ticket_number_str) is_ticket_number_assigned = True except Exception as exc: print("asssign ticket number to ticket-price field Exception:") print(exc) try: target_area.clear() target_area.send_keys("1") is_ticket_number_assigned = True except Exception as exc2: print("asssign ticket number to ticket-price still failed.") pass else: if show_debug_message: print("value already assigned.") # already assigned. is_ticket_number_assigned = True return is_dom_ready, is_ticket_number_assigned, is_need_refresh def kktix_check_agree_checkbox(driver, config_dict): show_debug_message = config_dict["advanced"]["verbose"] is_finish_checkbox_click = False is_dom_ready = False try: html_body = driver.page_source #print("html_body:",len(html_body)) if len(html_body) > 0: if not "{{'new.i_read_and_agree_to'" in html_body: is_dom_ready = True except Exception as exc: if show_debug_message: print(exc) pass if is_dom_ready: is_finish_checkbox_click = check_checkbox(driver, By.CSS_SELECTOR, '#person_agree_terms') #print("status:", is_dom_ready, is_finish_checkbox_click) return is_dom_ready, is_finish_checkbox_click def check_checkbox(driver, by, query): show_debug_message = True # debug. show_debug_message = False # online agree_checkbox = None try: agree_checkbox = driver.find_element(by, query) except Exception as exc: if show_debug_message: print(exc) pass is_checkbox_checked = False if not agree_checkbox is None: is_checkbox_checked = force_check_checkbox(driver, agree_checkbox) return is_checkbox_checked def force_check_checkbox(driver, agree_checkbox): is_finish_checkbox_click = False if not agree_checkbox is None: is_visible = False try: if agree_checkbox.is_enabled(): is_visible = True except Exception as exc: pass if is_visible: is_checkbox_checked = False try: if agree_checkbox.is_selected(): is_checkbox_checked = True except Exception as exc: pass if not is_checkbox_checked: #print('send check to checkbox') try: agree_checkbox.click() is_finish_checkbox_click = True except Exception as exc: try: driver.execute_script("arguments[0].click();", agree_checkbox) is_finish_checkbox_click = True except Exception as exc: pass else: is_finish_checkbox_click = True return is_finish_checkbox_click # PS: no double check, NOW. def kktix_double_check_all_text_value(driver, ticket_number): is_do_press_next_button = False # double check ticket input textbox. ticket_price_input_list = None try: # PS: unable directly access text's value attribute via css selector or xpath on KKTix! my_css_selector = "input[type='text']" #print("my_css_selector:", my_css_selector) ticket_price_input_list = driver.find_elements(By.CSS_SELECTOR, my_css_selector) except Exception as exc: pass if not ticket_price_input_list is None: #print("bingo, found one of ticket number textbox.") for ticket_price_input in ticket_price_input_list: current_ticket_number = "" try: current_ticket_number = str(ticket_price_input.get_attribute('value')).strip() except Exception as exc: pass if current_ticket_number is None: current_ticket_number = "" if len(current_ticket_number) > 0: if current_ticket_number == str(ticket_number): #print("bingo, match target ticket number.") # ONLY, this case to auto press next button. is_do_press_next_button = True break return is_do_press_next_button def set_kktix_control_label_text(driver, config_dict): fail_list = [] answer_list = util.get_answer_list_from_user_guess_string(config_dict, CONST_MAXBOT_ANSWER_ONLINE_FILE) inferred_answer_string = "" for answer_item in answer_list: if not answer_item in fail_list: inferred_answer_string = answer_item break input_text_css = 'div > div.code-input > div.control-group > div.controls > label[ng-if] > input[type="text"]' next_step_button_css = '#registrationsNewApp div.form-actions button.btn-primary' submit_by_enter = False check_input_interval = 0.2 is_answer_sent, fail_list = fill_common_verify_form(driver, config_dict, inferred_answer_string, fail_list, input_text_css, next_step_button_css, submit_by_enter, check_input_interval) def kktix_reg_captcha(driver, config_dict, fail_list, registrationsNewApp_div): show_debug_message = config_dict["advanced"]["verbose"] answer_list = [] is_question_popup = False question_text = get_text_by_selector(driver, 'div.custom-captcha-inner p', 'innerText') if len(question_text) > 0: is_question_popup = True write_question_to_file(question_text) answer_list = util.get_answer_list_from_user_guess_string(config_dict, CONST_MAXBOT_ANSWER_ONLINE_FILE) if len(answer_list)==0: if config_dict["advanced"]["auto_guess_options"]: answer_list = util.get_answer_list_from_question_string(registrationsNewApp_div, question_text) inferred_answer_string = "" for answer_item in answer_list: if not answer_item in fail_list: inferred_answer_string = answer_item break if len(answer_list) > 0: answer_list = list(dict.fromkeys(answer_list)) if show_debug_message: print("inferred_answer_string:", inferred_answer_string) print("answer_list:", answer_list) print("fail_list:", fail_list) # PS: auto-focus() when empty inferred_answer_string with empty inputed text value. if len(inferred_answer_string) > 0: input_text_css = 'div.custom-captcha-inner > div > div > input' next_step_button_css = '' submit_by_enter = False check_input_interval = 0.2 is_answer_sent, fail_list = fill_common_verify_form(driver, config_dict, inferred_answer_string, fail_list, input_text_css, next_step_button_css, submit_by_enter, check_input_interval) # due multi next buttons(pick seats/best seats) kktix_press_next_button(driver) time.sleep(0.5) fail_list.append(inferred_answer_string) #print("new fail_list:", fail_list) return fail_list, is_question_popup def kktix_reg_new_main(driver, config_dict, fail_list, played_sound_ticket): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True # read config. area_keyword = config_dict["area_auto_select"]["area_keyword"].strip() # part 1: check div. registrationsNewApp_div = None try: registrationsNewApp_div = driver.find_element(By.CSS_SELECTOR, '#registrationsNewApp') except Exception as exc: pass #print("find input fail:", exc) # part 2: assign ticket number is_ticket_number_assigned = False if not registrationsNewApp_div is None: is_dom_ready = True is_need_refresh = False if len(area_keyword) > 0: area_keyword_array = [] try: area_keyword_array = json.loads("["+ area_keyword +"]") except Exception as exc: area_keyword_array = [] # default refresh is_need_refresh_final = True for area_keyword_item in area_keyword_array: is_need_refresh_tmp = False is_dom_ready, is_ticket_number_assigned, is_need_refresh_tmp = kktix_assign_ticket_number(driver, config_dict, area_keyword_item) if not is_dom_ready: # page redirecting. break # one of keywords not need to refresh, final is not refresh. if not is_need_refresh_tmp: is_need_refresh_final = False if is_ticket_number_assigned: break else: if show_debug_message: print("is_need_refresh for keyword:", area_keyword_item) if not is_ticket_number_assigned: is_need_refresh = is_need_refresh_final else: # empty keyword, match all. is_dom_ready, is_ticket_number_assigned, is_need_refresh = kktix_assign_ticket_number(driver, config_dict, "") if is_dom_ready: # part 3: captcha if is_ticket_number_assigned: if config_dict["advanced"]["play_sound"]["ticket"]: if not played_sound_ticket: play_sound_while_ordering(config_dict) played_sound_ticket = True # whole event question. fail_list, is_question_popup = kktix_reg_captcha(driver, config_dict, fail_list, registrationsNewApp_div) # single option question if not is_question_popup: # no captcha text popup, goto next page. control_text = get_text_by_selector(driver, 'div > div.code-input > div.control-group > label.control-label', 'innerText') if show_debug_message: print("control_text:", control_text) if len(control_text) > 0: input_text_css = 'div > div.code-input > div.control-group > div.controls > label[ng-if] > input[type="text"]' input_text_element = None try: input_text_element = driver.find_element(By.CSS_SELECTOR, input_text_css) except Exception as exc: #print(exc) pass if input_text_element is None: radio_css = 'div > div.code-input > div.control-group > div.controls > label[ng-if] > input[type="radio"]' try: radio_element = driver.find_element(By.CSS_SELECTOR, radio_css) if radio_element: print("found radio") joined_button_css = 'div > div.code-input > div.control-group > div.controls > label[ng-if] > span[ng-if] > a[ng-href="#"]' joined_element = driver.find_element(By.CSS_SELECTOR, joined_button_css) if joined_element: control_text = "" print("member joined") except Exception as exc: print(exc) pass if len(control_text) == 0: click_ret = kktix_press_next_button(driver) else: # input by maxbox plus extension. is_fill_at_webdriver = False if not config_dict["browser"] in CONST_CHROME_FAMILY: is_fill_at_webdriver = True else: if not config_dict["advanced"]["chrome_extension"]: is_fill_at_webdriver = True # TODO: not implement in extension, so force to fill in webdriver. is_fill_at_webdriver = True if is_fill_at_webdriver: set_kktix_control_label_text(driver, config_dict) pass else: if is_need_refresh: # reset to play sound when ticket avaiable. played_sound_ticket = False try: print("no match any price, start to refresh page...") driver.refresh() except Exception as exc: #print("refresh fail") pass if config_dict["advanced"]["auto_reload_page_interval"] > 0: time.sleep(config_dict["advanced"]["auto_reload_page_interval"]) return fail_list, played_sound_ticket def kktix_check_register_status(driver, url): event_code = util.kktix_get_event_code(url) if len(event_code) > 0: js = ''' function load_kktix_register_code(){ let api_url = "https://kktix.com/g/events/%s/register_info"; fetch(api_url).then(function (response) { return response.json(); } ).then(function (data) { let reload=false; console.log(data.inventory.registerStatus); if(data.inventory.registerStatus=='OUT_OF_STOCK') {reload=true;} if(data.inventory.registerStatus=='COMING_SOON') {reload=true;} if(data.inventory.registerStatus=='SOLD_OUT') {reload=true;} console.log(reload); if(reload) {location.reload();} } ).catch(function (err) { console.log(err); }); } if (!$.kkUser) { $.kkUser = {}; } if (typeof $.kkUser.checked_status_register_code === 'undefined') { $.kkUser.checked_status_register_code = true; load_kktix_register_code(); } ''' % (event_code) try: driver.execute_script(js) except Exception as exc: pass registerStatus = None # use javascritp version only. is_match_event_code = False if is_match_event_code: registerStatus = util.kktix_get_registerStatus(event_code) return registerStatus def kktix_reg_auto_reload(driver, url, config_dict): # auto reload javascrit code at chrome extension. is_reload_at_webdriver = False if not config_dict["browser"] in CONST_CHROME_FAMILY: is_reload_at_webdriver = True else: if not config_dict["advanced"]["chrome_extension"]: is_reload_at_webdriver = True if is_reload_at_webdriver: kktix_check_register_status(driver, url) # PURPOSE: get target area list. # PS: this is main block, use keyword to get rows. def get_fami_target_area(driver, config_dict, area_keyword_item): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True date_keyword = config_dict["date_auto_select"]["date_keyword"].strip() date_keyword = util.format_keyword_string(date_keyword) auto_select_mode = config_dict["area_auto_select"]["mode"] area_list = None try: my_css_selector = "table.session__list > tbody > tr" area_list = driver.find_elements(By.CSS_SELECTOR, my_css_selector) except Exception as exc: print("find #session date list fail") if show_debug_message: print(exc) #PS: some blocks are generate by ajax, not appear at first time. formated_area_list = None if not area_list is None: area_list_length = len(area_list) if show_debug_message: print("lenth of area rows:", area_list_length) if area_list_length > 0: formated_area_list = [] # filter list. for row in area_list: row_is_enabled=True el_btn = None try: my_css_selector = "button" el_btn = row.find_element(By.TAG_NAME, my_css_selector) if not el_btn is None: if not el_btn.is_enabled(): #print("row's button disabled!") row_is_enabled=False except Exception as exc: if show_debug_message: print(exc) pass if row_is_enabled: formated_area_list.append(row) matched_blocks = None if not formated_area_list is None: if len(formated_area_list) > 0: matched_blocks = [] if len(date_keyword)==0 and len(area_keyword_item)==0: # select all. matched_blocks = formated_area_list else: # match keyword. for row in formated_area_list: date_html_text = "" area_html_text = "" row_text = "" row_html = "" try: my_css_selector = "td:nth-child(1)" td_date = row.find_element(By.CSS_SELECTOR, my_css_selector) if not td_date is None: #print("date:", td_date.text) date_html_text = util.format_keyword_string(td_date.text) my_css_selector = "td:nth-child(2)" td_area = row.find_element(By.CSS_SELECTOR, my_css_selector) if not td_area is None: #print("area:", td_area.text) area_html_text = util.format_keyword_string(td_area.text) #row_text = row.text row_html = row.get_attribute('innerHTML') row_text = util.remove_html_tags(row_html) except Exception as exc: if show_debug_message: print(exc) # error, exit loop break if len(row_text) > 0: if util.reset_row_text_if_match_keyword_exclude(config_dict, row_text): row_text = "" if len(row_text) > 0: # check date. is_match_date = False if len(date_keyword) > 0: if date_keyword in date_html_text: #print("is_match_date") is_match_date = True else: is_match_date = True # check area. is_match_area = False if len(area_keyword_item) > 0: # must match keyword. is_match_area = True area_keyword_array = area_keyword_item.split(' ') for area_keyword in area_keyword_array: area_keyword = util.format_keyword_string(area_keyword) if not area_keyword in row_text: is_match_area = False break else: # without keyword. is_match_area = True if is_match_date and is_match_area: matched_blocks.append(row) if auto_select_mode == CONST_FROM_TOP_TO_BOTTOM: #print("only need first item, break area list loop.") break return_row_count = 0 if not matched_blocks is None: return_row_count = len(matched_blocks) if return_row_count==0: matched_blocks = None if show_debug_message: print("return_row_count:", return_row_count) return matched_blocks def fami_verify(driver, config_dict, fail_list): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True answer_list = [] question_text = "" #if len(question_text) > 0: if True: #write_question_to_file(question_text) answer_list = util.get_answer_list_from_user_guess_string(config_dict, CONST_MAXBOT_ANSWER_ONLINE_FILE) if len(answer_list)==0: if config_dict["advanced"]["auto_guess_options"]: answer_list = util.guess_tixcraft_question(driver, question_text) inferred_answer_string = "" for answer_item in answer_list: if not answer_item in fail_list: inferred_answer_string = answer_item break if show_debug_message: print("inferred_answer_string:", inferred_answer_string) print("answer_list:", answer_list) # PS: auto-focus() when empty inferred_answer_string with empty inputed text value. input_text_css = "#verifyPrefAnswer" next_step_button_css = "" submit_by_enter = True check_input_interval = 0.2 is_answer_sent, fail_list = fill_common_verify_form(driver, config_dict, inferred_answer_string, fail_list, input_text_css, next_step_button_css, submit_by_enter, check_input_interval) return fail_list def fami_activity(driver): #print("fami_activity bingo") #--------------------------- # part 1: press "buy" button. #--------------------------- fami_start_to_buy_button = None try: fami_start_to_buy_button = driver.find_element(By.CSS_SELECTOR, '#buyWaiting') except Exception as exc: pass is_visible = False is_need_refresh = False if not fami_start_to_buy_button is None: try: if fami_start_to_buy_button.is_enabled(): is_visible = True except Exception as exc: pass else: is_need_refresh = True if is_visible: try: fami_start_to_buy_button.click() except Exception as exc: print("click buyWaiting button fail...") #print(exc) #pass try: js = """arguments[0].scrollIntoView(); arguments[0].firstChild.click(); """ #driver.execute_script(js, fami_start_to_buy_button) except Exception as exc: pass if is_need_refresh: try: driver.refresh() except Exception as exc: pass def fami_date_auto_select(driver, config_dict, last_activity_url): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True auto_select_mode = config_dict["date_auto_select"]["mode"] date_keyword = config_dict["date_auto_select"]["date_keyword"].strip() auto_reload_coming_soon_page_enable = config_dict["tixcraft"]["auto_reload_coming_soon_page"] if show_debug_message: print("date_keyword:", date_keyword) print("auto_reload_coming_soon_page_enable:", auto_reload_coming_soon_page_enable) matched_blocks = None area_list = None try: my_css_selector = ".session__list > tbody > tr" area_list = driver.find_elements(By.CSS_SELECTOR, my_css_selector) except Exception as exc: print("find date-time rows fail") print(exc) #PS: some blocks are generate by ajax, not appear at first time. formated_area_list = None if not area_list is None: area_list_count = len(area_list) if show_debug_message: print("date_list_count:", area_list_count) if area_list_count > 0: formated_area_list = [] # filter list. for row in area_list: row_text = "" row_html = "" try: #row_text = row.text row_html = row.get_attribute('innerHTML') row_text = util.remove_html_tags(row_html) except Exception as exc: if show_debug_message: print(exc) # error, exit loop break if len(row_text) > 0: if " 0: formated_area_list.append(row) if not formated_area_list is None: area_list_count = len(formated_area_list) if show_debug_message: print("formated_area_list count:", area_list_count) if area_list_count > 0: if len(date_keyword) == 0: matched_blocks = formated_area_list else: # match keyword. if show_debug_message: print("start to match keyword:", date_keyword) matched_blocks = util.get_matched_blocks_by_keyword(config_dict, auto_select_mode, date_keyword, formated_area_list) if show_debug_message: if not matched_blocks is None: print("after match keyword, found count:", len(matched_blocks)) else: print("not found date-time-position") pass else: print("date date-time-position is None") pass target_area = util.get_target_item_from_matched_list(matched_blocks, auto_select_mode) is_date_assign_by_bot = False if not target_area is None: is_button_clicked = False for i in range(3): el_btn = None try: my_css_selector = "button" el_btn = target_area.find_element(By.CSS_SELECTOR, my_css_selector) except Exception as exc: pass if not el_btn is None: try: if el_btn.is_enabled(): el_btn.click() print("buy icon pressed.") is_button_clicked = True except Exception as exc: pass # use plan B ''' try: print("force to click by js.") driver.execute_script("arguments[0].click();", el_btn) ret = True except Exception as exc: pass ''' if is_button_clicked: break is_date_assign_by_bot = is_button_clicked else: # no target to click. if auto_reload_coming_soon_page_enable: # auto refresh for date list page. if not formated_area_list is None: if len(formated_area_list) == 0: try: #driver.refresh() driver.get(last_activity_url) time.sleep(0.3) except Exception as exc: pass if config_dict["advanced"]["auto_reload_page_interval"] > 0: time.sleep(config_dict["advanced"]["auto_reload_page_interval"]) return is_date_assign_by_bot def fami_area_auto_select(driver, config_dict, area_keyword_item): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True auto_select_mode = config_dict["area_auto_select"]["mode"] is_price_assign_by_bot = False is_need_refresh = False area_list = None try: my_css_selector = "div > a.area" area_list = driver.find_elements(By.CSS_SELECTOR, my_css_selector) except Exception as exc: print("find a.area list fail") print(exc) formated_area_list = None if not area_list is None: area_list_count = len(area_list) if show_debug_message: print("area_list_count:", area_list_count) print("area_keyword_item:", area_keyword_item) if area_list_count > 0: formated_area_list = [] # filter list. for row in area_list: row_text = "" row_html = "" try: #row_text = row.text row_html = row.get_attribute('innerHTML') row_text = util.remove_html_tags(row_html) except Exception as exc: if show_debug_message: print(exc) # error, exit loop break if '售完' in row_text: row_text = "" if '"area disabled"' in row_html: row_text = "" if len(row_text) > 0: if util.reset_row_text_if_match_keyword_exclude(config_dict, row_text): row_text = "" if len(row_text) > 0: formated_area_list.append(row) else: if show_debug_message: print("area_list_count is empty.") pass else: if show_debug_message: print("area_list_count is None.") pass if is_price_assign_by_bot: formated_area_list = None matched_blocks = [] if not formated_area_list is None: area_list_count = len(formated_area_list) if show_debug_message: print("formated_area_list count:", area_list_count) if area_list_count > 0: if len(area_keyword_item) == 0: matched_blocks = formated_area_list else: for row in formated_area_list: row_text = "" row_html = "" try: #row_text = row.text row_html = row.get_attribute('innerHTML') row_text = util.remove_html_tags(row_html) except Exception as exc: if show_debug_message: print(exc) # error, exit loop break if len(row_text) > 0: row_text = util.format_keyword_string(row_text) if show_debug_message: print("row_text:", row_text) is_match_area = False if len(area_keyword_item) > 0: # must match keyword. is_match_area = True area_keyword_array = area_keyword_item.split(' ') for area_keyword in area_keyword_array: area_keyword = util.format_keyword_string(area_keyword) if not area_keyword in row_text: is_match_area = False break else: # without keyword. is_match_area = True if is_match_area: matched_blocks.append(row) if auto_select_mode == CONST_FROM_TOP_TO_BOTTOM: break if show_debug_message: print("after match keyword, found count:", len(matched_blocks)) target_area = util.get_target_item_from_matched_list(matched_blocks, auto_select_mode) if not matched_blocks is None: if len(matched_blocks) == 0: is_need_refresh = True if show_debug_message: print("matched_blocks is empty, is_need_refresh") if not target_area is None: try: if target_area.is_enabled(): target_area.click() is_price_assign_by_bot = True except Exception as exc: print("click target_area link fail") print(exc) # use plan B try: print("force to click by js.") driver.execute_script("arguments[0].click();", target_area) is_price_assign_by_bot = True except Exception as exc: pass return is_need_refresh, is_price_assign_by_bot def fami_date_to_area(driver, config_dict, last_activity_url): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True is_price_assign_by_bot = False is_need_refresh = False # click price row. area_keyword = config_dict["area_auto_select"]["area_keyword"].strip() if show_debug_message: print("area_keyword:", area_keyword) is_need_refresh = False if len(area_keyword) > 0: area_keyword_array = [] try: area_keyword_array = json.loads("["+ area_keyword +"]") except Exception as exc: area_keyword_array = [] for area_keyword_item in area_keyword_array: is_need_refresh, is_price_assign_by_bot = fami_area_auto_select(driver, config_dict, area_keyword_item) if not is_need_refresh: break else: print("is_need_refresh for keyword:", area_keyword_item) else: # empty keyword, match all. is_need_refresh, is_price_assign_by_bot = fami_area_auto_select(driver, config_dict, area_keyword) if show_debug_message: print("is_need_refresh:", is_need_refresh) if is_need_refresh: try: #driver.refresh() #driver.get(last_activity_url) pass except Exception as exc: pass if config_dict["advanced"]["auto_reload_page_interval"] > 0: time.sleep(config_dict["advanced"]["auto_reload_page_interval"]) return is_price_assign_by_bot def fami_home_auto_select(driver, config_dict, last_activity_url): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True is_ticket_number_assigned = False ticket_number = str(config_dict["ticket_number"]) #--------------------------- # part 3: fill ticket number. #--------------------------- ticket_el = None is_date_assign_by_bot = False is_price_assign_by_bot = False try: my_css_selector = "tr.ticket > td > select" ticket_el = driver.find_element(By.CSS_SELECTOR, my_css_selector) except Exception as exc: print("find ticket select element") pass #print(exc) if ticket_el is None: print("try to find datetime table list") is_date_assign_by_bot = fami_date_auto_select(driver, config_dict, last_activity_url) is_price_assign_by_bot = fami_date_to_area(driver, config_dict, last_activity_url) is_select_box_visible = False if not ticket_el is None: try: if ticket_el.is_enabled(): is_select_box_visible = True except Exception as exc: pass is_ticket_number_assigned = False if is_select_box_visible: ticket_number_select = None try: ticket_number_select = Select(ticket_el) except Exception as exc: pass if not ticket_number_select is None: try: #print("get select ticket value:" + Select(ticket_number_select).first_selected_option.text) if ticket_number_select.first_selected_option.text=="0" or ticket_number_select.first_selected_option.text=="選擇張數": # target ticket number ticket_number_select.select_by_visible_text(ticket_number) is_ticket_number_assigned = True except Exception as exc: print("select_by_visible_text ticket_number fail") print(exc) try: # try target ticket number twice ticket_number_select.select_by_visible_text(ticket_number) is_ticket_number_assigned = True except Exception as exc: print("select_by_visible_text ticket_number fail...2") print(exc) # try buy one ticket try: ticket_number_select.select_by_visible_text("1") is_ticket_number_assigned = True except Exception as exc: print("select_by_visible_text 1 fail") pass #--------------------------- # part 4: press "next" button. #--------------------------- if is_ticket_number_assigned: fami_assign_site_button = None try: my_css_selector = "div.col > a.btn" fami_assign_site_button = driver.find_element(By.CSS_SELECTOR, my_css_selector) except Exception as exc: pass if not fami_assign_site_button is None: is_visible = False try: if fami_assign_site_button.is_enabled(): is_visible = True except Exception as exc: pass if is_visible: try: fami_assign_site_button.click() except Exception as exc: print("click buyWaiting button fail") #print(exc) try: driver.execute_script("arguments[0].click();", fami_assign_site_button) except Exception as exc: pass matched_blocks = None if not is_select_box_visible: #--------------------------- # part 2: select keywords #--------------------------- area_keyword = config_dict["area_auto_select"]["area_keyword"].strip() if len(area_keyword) > 0: area_keyword_array = [] try: area_keyword_array = json.loads("["+ area_keyword +"]") except Exception as exc: area_keyword_array = [] for area_keyword_item in area_keyword_array: matched_blocks = get_fami_target_area(driver, config_dict, area_keyword_item) if not matched_blocks is None: break else: print("is_need_refresh for keyword:", area_keyword_item) else: # empty keyword, match all. matched_blocks = get_fami_target_area(driver, config_dict, "") auto_select_mode = config_dict["area_auto_select"]["mode"] target_area = util.get_target_item_from_matched_list(matched_blocks, auto_select_mode) if not target_area is None: el_btn = None is_visible = False try: my_css_selector = "button" el_btn = target_area.find_element(By.TAG_NAME, my_css_selector) if not el_btn is None: if el_btn.is_enabled(): is_visible = True except Exception as exc: pass if is_visible: try: el_btn.click() except Exception as exc: print("click buy button fail, start to retry...") try: driver.execute_script("arguments[0].click();", el_btn) except Exception as exc: pass return is_date_assign_by_bot # purpose: date auto select def urbtix_date_auto_select(driver, auto_select_mode, date_keyword, auto_reload_coming_soon_page_enable): show_debug_message = True # debug. show_debug_message = False # online ret = False matched_blocks = None area_list = None try: #print("try to find cityline area block") my_css_selector = "div.conent-wrapper > div.list-wrapper > ul" area_list = driver.find_elements(By.CSS_SELECTOR, my_css_selector) except Exception as exc: print("find #date-time-position date list fail") print(exc) #PS: some blocks are generate by ajax, not appear at first time. formated_area_list = None if not area_list is None: area_list_count = len(area_list) if show_debug_message: print("date_list_count:", area_list_count) if area_list_count > 0: formated_area_list = [] # filter list. for row in area_list: row_is_enabled=True el_btn = None try: my_css_selector = "div.buy-icon" el_btn = row.find_element(By.CSS_SELECTOR, my_css_selector) if not el_btn is None: button_class_string = str(el_btn.get_attribute('class')) if len(button_class_string) > 1: if 'disabled' in button_class_string: row_is_enabled=False except Exception as exc: if show_debug_message: print(exc) pass if row_is_enabled: formated_area_list.append(row) if show_debug_message: print("formated_area_list count:", len(formated_area_list)) if len(date_keyword) == 0: matched_blocks = formated_area_list else: # match keyword. if show_debug_message: print("start to match keyword:", date_keyword) matched_blocks = [] for row in formated_area_list: row_text = "" row_html = "" try: #row_text = row.text row_html = row.get_attribute('innerHTML') row_text = util.remove_html_tags(row_html) except Exception as exc: if show_debug_message: print(exc) # error, exit loop break if len(row_text) > 0: if show_debug_message: print("row_text:", row_text) is_match_area = util.is_row_match_keyword(date_keyword, row_text) if is_match_area: matched_blocks.append(row) if auto_select_mode == CONST_FROM_TOP_TO_BOTTOM: #print("only need first item, break area list loop.") break if show_debug_message: if not matched_blocks is None: print("after match keyword, found count:", len(matched_blocks)) else: print("not found date-time-position") pass else: print("date date-time-position is None") pass target_area = util.get_target_item_from_matched_list(matched_blocks, auto_select_mode) if not target_area is None: el_btn = None try: #print("target_area text", target_area.text) my_css_selector = "div.buy-icon" el_btn = target_area.find_element(By.CSS_SELECTOR, my_css_selector) except Exception as exc: pass if not el_btn is None: is_button_enable = True try: if not el_btn.is_enabled(): is_button_enable = False else: # button enable, but class disable. button_class_string = str(el_btn.get_attribute('class')) if len(button_class_string) > 1: if 'disabled' in button_class_string: is_button_enable = False if is_button_enable: el_btn.click() ret = True print("buy icon pressed.") except Exception as exc: # use plan B try: print("force to click by js.") driver.execute_script("arguments[0].click();", el_btn) ret = True except Exception as exc: pass else: # no target. if auto_reload_coming_soon_page_enable: # auto refresh for date list page. if not formated_area_list is None: if len(formated_area_list) == 0: try: driver.refresh() time.sleep(1.0) except Exception as exc: pass return ret def urbtix_purchase_ticket(driver, config_dict): show_debug_message = True # debug. show_debug_message = False # online date_auto_select_mode = config_dict["date_auto_select"]["mode"] date_keyword = config_dict["date_auto_select"]["date_keyword"].strip() auto_reload_coming_soon_page_enable = config_dict["tixcraft"]["auto_reload_coming_soon_page"] if show_debug_message: print("date_keyword:", date_keyword) is_date_assign_by_bot = urbtix_date_auto_select(driver, date_auto_select_mode, date_keyword, auto_reload_coming_soon_page_enable) return is_date_assign_by_bot # purpose: area auto select def urbtix_area_auto_select(driver, config_dict, area_keyword_item): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True auto_select_mode = config_dict["area_auto_select"]["mode"] is_price_assign_by_bot = False is_need_refresh = False matched_blocks = None area_list = None try: #print("try to find cityline area block") my_css_selector = "div.area-list > div.area-wrapper" area_list = driver.find_elements(By.CSS_SELECTOR, my_css_selector) except Exception as exc: print("find #ticket-price-tbl date list fail") print(exc) formated_area_list = None if not area_list is None: area_list_count = len(area_list) if show_debug_message: print("area_list_count:", area_list_count) print("area_keyword_item:", area_keyword_item) if area_list_count > 0: formated_area_list = [] # filter list. for row in area_list: row_text = "" row_html = "" try: #row_text = row.text row_html = row.get_attribute('innerHTML') row_text = util.remove_html_tags(row_html) except Exception as exc: if show_debug_message: print(exc) # error, exit loop break if len(row_text) > 0: if util.reset_row_text_if_match_keyword_exclude(config_dict, row_text): row_text = "" if len(row_text) > 0: if ' disabled' in row_html: row_text = "" if ' 售罄' in row_html: row_text = "" if len(row_text) > 0: if '