#!/usr/bin/env python3 #encoding=utf-8 #執行方式:python chrome_tixcraft.py 或 python3 chrome_tixcraft.py #import jieba #from DrissionPage import ChromiumPage import argparse import base64 import json import logging import os import pathlib import platform import random import re import ssl import sys 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 from NonBrowser import NonBrowser try: import ddddocr except Exception as exc: pass CONST_APP_VERSION = "MaxBot (2024.01.19)" 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 =[ "*google-analytics.com/*", "*googletagmanager.com/*", "*googletagservices.com/*", "*lndata.com/*", "*a.amnet.tw/*", "*adx.c.appier.net/*", "*clarity.ms/*", "*cloudfront.com/*", "*cms.analytics.yahoo.com/*", "*doubleclick.net/*", "*e2elog.fetnet.net/*", "*fundingchoicesmessages.google.com/*", "*ghtinc.com/*", "*match.adsrvr.org/*", "*onead.onevision.com.tw/*", "*popin.cc/*", "*rollbar.com/*", "*sb.scorecardresearch.com/*", "*tagtoo.co/*", "*.ssp.hinet.net/*", "*ticketmaster.sg/js/adblock*", "*.googlesyndication.com/*", "*treasuredata.com/*", "*play.google.com/log?*", "*www.youtube.com/youtubei/v1/player/heartbeat*", "*tixcraft.com/js/analytics.js*", "*ticketmaster.sg/js/adblock.js*", "*img.uniicreative.com/*", "*cdn.cookielaw.org/*", "*tixcraft.com/js/custom.js*", "*tixcraft.com/js/common.js*", "*cdnjs.cloudflare.com/ajax/libs/clipboard.js/*"] 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" CONST_SELECT_ORDER_DEFAULT = CONST_FROM_TOP_TO_BOTTOM 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_CHROME_FAMILY = ["chrome","edge","brave"] USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" warnings.simplefilter('ignore',InsecureRequestWarning) ssl._create_default_https_context = ssl._create_unverified_context logging.basicConfig() logger = logging.getLogger('logger') def t_or_f(arg): ret = False ua = str(arg).upper() if 'TRUE'.startswith(ua): ret = True elif 'YES'.startswith(ua): ret = True return ret def format_config_keyword_for_json(user_input): if len(user_input) > 0: if not ('\"' in user_input): user_input = '"' + user_input + '"' if user_input[:1]=="{" and user_input[-1:]=="}": user_input=user_input[1:] user_input=user_input[:-1] if user_input[:1]=="[" and user_input[-1:]=="]": user_input=user_input[1:] user_input=user_input[:-1] return user_input def remove_html_tags(text): ret = "" if not text is None: clean = re.compile('<.*?>') ret = re.sub(clean, '', text) ret = ret.strip() return ret def sx(s1): key=18 return ''.join(chr(ord(a) ^ key) for a in s1) def decryptMe(b): s="" if(len(b)>0): s=sx(base64.b64decode(b).decode("UTF-8")) return s def encryptMe(s): data="" if(len(s)>0): data=base64.b64encode(sx(s).encode('UTF-8')).decode("UTF-8") return data def get_app_root(): # 讀取檔案裡的參數值 basis = "" if hasattr(sys, 'frozen'): basis = sys.executable else: basis = sys.argv[0] app_root = os.path.dirname(basis) return app_root def get_config_dict(args): app_root = get_app_root() config_filepath = os.path.join(app_root, CONST_MAXBOT_CONFIG_FILE) # allow assign config by command line. if not args.input is None: if len(args.input) > 0: 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 not args.headless is None: headless_flag = t_or_f(args.headless) if headless_flag: config_dict["advanced"]["headless"] = True if not args.homepage is None: if len(args.homepage) > 0: config_dict["homepage"] = args.homepage if not args.ticket_number is None: if args.homepage > 0: config_dict["ticket_number"] = args.ticket_number if not args.browser is None: if len(args.browser) > 0: config_dict["browser"] = args.browser if not args.tixcraft_sid is None: if len(args.tixcraft_sid) > 0: config_dict["advanced"]["tixcraft_sid"] = encryptMe(args.tixcraft_sid) if not args.kktix_account is None: if len(args.kktix_account) > 0: config_dict["advanced"]["kktix_account"] = args.kktix_account if not args.kktix_password is None: if len(args.kktix_password) > 0: config_dict["advanced"]["kktix_password"] = args.kktix_password if not args.ibonqware is None: if len(args.ibonqware) > 0: config_dict["advanced"]["ibonqware"] = encryptMe(args.ibonqware) if not args.proxy_server is None: if len(args.proxy_server) > 2: config_dict["advanced"]["proxy_server_port"] = args.proxy_server # special case for headless to enable away from keyboard mode. is_headless_enable = False if config_dict["advanced"]["headless"]: # for tixcraft headless. if len(config_dict["advanced"]["tixcraft_sid"]) > 1: is_headless_enable = True else: print("If you are runnig headless mode on tixcraft, you need input your cookie SID.") if is_headless_enable: config_dict["ocr_captcha"]["enable"] = True config_dict["ocr_captcha"]["force_submit"] = True return config_dict def write_string_to_file(filename, data): outfile = None if platform.system() == 'Windows': outfile = open(filename, 'w', encoding='UTF-8') else: outfile = open(filename, 'w') if not outfile is None: outfile.write("%s" % data) 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) 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) 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 format_keyword_string(keyword): if not keyword is None: if len(keyword) > 0: keyword = keyword.replace('/','/') keyword = keyword.replace(' ','') keyword = keyword.replace(',','') keyword = keyword.replace(',','') keyword = keyword.replace('$','') keyword = keyword.replace(' ','').lower() return keyword def format_quota_string(formated_html_text): formated_html_text = formated_html_text.replace('「','【') formated_html_text = formated_html_text.replace('『','【') formated_html_text = formated_html_text.replace('〔','【') formated_html_text = formated_html_text.replace('﹝','【') formated_html_text = formated_html_text.replace('〈','【') formated_html_text = formated_html_text.replace('《','【') formated_html_text = formated_html_text.replace('[','【') formated_html_text = formated_html_text.replace('〖','【') formated_html_text = formated_html_text.replace('[','【') formated_html_text = formated_html_text.replace('(','【') formated_html_text = formated_html_text.replace('(','【') formated_html_text = formated_html_text.replace('」','】') formated_html_text = formated_html_text.replace('』','】') formated_html_text = formated_html_text.replace('〕','】') formated_html_text = formated_html_text.replace('﹞','】') formated_html_text = formated_html_text.replace('〉','】') formated_html_text = formated_html_text.replace('》','】') formated_html_text = formated_html_text.replace(']','】') formated_html_text = formated_html_text.replace('〗','】') formated_html_text = formated_html_text.replace(']','】') formated_html_text = formated_html_text.replace(')','】') formated_html_text = formated_html_text.replace(')','】') return formated_html_text def full2half(keyword): n = "" if not keyword is None: if len(keyword) > 0: for char in keyword: num = ord(char) if num == 0x3000: num = 32 elif 0xFF01 <= num <= 0xFF5E: num -= 0xfee0 n += chr(num) return n def get_chinese_numeric(): my_dict = {} my_dict['0']=['0','0','zero','零'] my_dict['1']=['1','1','one','一','壹','①','❶','⑴'] my_dict['2']=['2','2','two','二','貳','②','❷','⑵'] my_dict['3']=['3','3','three','三','叁','③','❸','⑶'] my_dict['4']=['4','4','four','四','肆','④','❹','⑷'] my_dict['5']=['5','5','five','五','伍','⑤','❺','⑸'] my_dict['6']=['6','6','six','六','陸','⑥','❻','⑹'] my_dict['7']=['7','7','seven','七','柒','⑦','❼','⑺'] my_dict['8']=['8','8','eight','八','捌','⑧','❽','⑻'] my_dict['9']=['9','9','nine','九','玖','⑨','❾','⑼'] return my_dict # 同義字 def synonym_dict(char): ret = [] my_dict = get_chinese_numeric() if char in my_dict: ret = my_dict[char] else: ret.append(char) return ret def chinese_numeric_to_int(char): ret = None my_dict = get_chinese_numeric() for i in my_dict: for item in my_dict[i]: if char.lower() == item: ret = int(i) break if not ret is None: break return ret def normalize_chinese_numeric(keyword): ret = "" for char in keyword: converted_int = chinese_numeric_to_int(char) if not converted_int is None: ret += str(converted_int) return ret def find_continuous_number(text): chars = "0123456789" return find_continuous_pattern(chars, text) def find_continuous_text(text): chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" return find_continuous_pattern(chars, text) def find_continuous_pattern(allowed_char, text): ret = "" is_allowed_char_start = False for char in text: #print("char:", char) if char in allowed_char: if len(ret)==0 and not is_allowed_char_start: is_allowed_char_start = True if is_allowed_char_start: ret += char else: # make not continuous is_allowed_char_start = False return ret def is_all_alpha_or_numeric(text): ret = False alpha_count = 0 numeric_count = 0 for char in text: try: if char.encode('UTF-8').isalpha(): alpha_count += 1 except Exception as exc: pass #if char.isnumeric(): if char.isdigit(): numeric_count += 1 if (alpha_count + numeric_count) == len(text): ret = True #print("text/is_all_alpha_or_numeric:",text,ret) 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_brave_bin_path(): brave_path = "" if platform.system() == 'Windows': brave_path = "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe" if not os.path.exists(brave_path): brave_path = os.path.expanduser('~') + "\\AppData\\Local\\BraveSoftware\\Brave-Browser\\Application\\brave.exe" if not os.path.exists(brave_path): brave_path = "C:\\Program Files (x86)\\BraveSoftware\\Brave-Browser\\Application\\brave.exe" if not os.path.exists(brave_path): brave_path = "D:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe" if platform.system() == 'Linux': brave_path = "/usr/bin/brave-browser" if platform.system() == 'Darwin': brave_path = '/Applications/Brave Browser.app/Contents/MacOS/Brave Browser' return brave_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-blink-features=AutomationControlled") chrome_options.add_argument("--disable-infobars") 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-setuid-sandbox") chrome_options.add_argument("--disable-site-isolation-trials") chrome_options.add_argument("--disable-smooth-scrolling") chrome_options.add_argument("--disable-sync") chrome_options.add_argument("--no-sandbox"); chrome_options.add_argument('--disable-features=TranslateUI') chrome_options.add_argument('--disable-translate') chrome_options.add_argument('--disable-web-security') chrome_options.add_argument('--lang=zh-TW') # 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", {"credentials_enable_service": False, "profile.password_manager_enabled": False, "translate":{"enabled": False}}) 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 = 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 = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True driver = None Root_Dir = 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("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 Exception as exc: error_message = str(exc) if show_debug_message: print(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) # 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 Exception 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 Exception as exc3: print(exc3) pass return driver def clean_uc_exe_cache(): exe_name = "chromedriver%s" platform = sys.platform if platform.endswith("win32"): exe_name %= ".exe" if platform.endswith(("linux", "linux2")): exe_name %= "" if platform.endswith("darwin"): exe_name %= "" d = "" if platform.endswith("win32"): d = "~/appdata/roaming/undetected_chromedriver" elif "LAMBDA_TASK_ROOT" in os.environ: d = "/tmp/undetected_chromedriver" elif platform.startswith(("linux", "linux2")): d = "~/.local/share/undetected_chromedriver" elif platform.endswith("darwin"): d = "~/Library/Application Support/undetected_chromedriver" else: d = "~/.undetected_chromedriver" data_path = os.path.abspath(os.path.expanduser(d)) is_cache_exist = False p = pathlib.Path(data_path) files = list(p.rglob("*chromedriver*?")) for file in files: if os.path.exists(str(file)): is_cache_exist = True try: os.unlink(str(file)) except Exception as exc2: print(exc2) pass return is_cache_exist def dump_settings_to_maxbot_plus_extension(ext, config_dict): # sync config. target_path = ext target_path = os.path.join(target_path, "data") target_path = os.path.join(target_path, CONST_MAXBOT_CONFIG_FILE) #print("save as to:", target_path) if os.path.isfile(target_path): try: #print("remove file:", target_path) os.unlink(target_path) except Exception as exc: pass with open(target_path, 'w') as outfile: json.dump(config_dict, outfile) # add host_permissions target_path = ext target_path = os.path.join(target_path, "manifest.json") manifest_dict = None if os.path.isfile(target_path): with open(target_path) as json_data: manifest_dict = json.load(json_data) local_remote_url_array = [] local_remote_url = config_dict["advanced"]["remote_url"] if len(local_remote_url) > 0: try: temp_remote_url_array = json.loads("["+ local_remote_url +"]") for remote_url in temp_remote_url_array: remote_url_final = remote_url + "*" local_remote_url_array.append(remote_url_final) except Exception as exc: pass if len(local_remote_url_array) > 0: is_manifest_changed = False for remote_url_final in local_remote_url_array: if not remote_url_final in manifest_dict["host_permissions"]: #print("local remote_url not in manifest:", remote_url_final) manifest_dict["host_permissions"].append(remote_url_final) is_manifest_changed = True if is_manifest_changed: json_str = json.dumps(manifest_dict, indent=4) with open(target_path, 'w') as outfile: outfile.write(json_str) def dump_settings_to_maxblock_plus_extension(ext, config_dict): # sync config. target_path = ext target_path = os.path.join(target_path, "data") # special case, due to data folder is empty, sometime will be removed. if not os.path.exists(target_path): os.mkdir(target_path) target_path = os.path.join(target_path, CONST_MAXBOT_CONFIG_FILE) #print("save as to:", target_path) if os.path.isfile(target_path): try: #print("remove file:", target_path) os.unlink(target_path) except Exception as exc: pass with open(target_path, 'w') as outfile: config_dict["domain_filter"]=CONST_MAXBLOCK_EXTENSION_FILTER json.dump(config_dict, outfile) 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: dump_settings_to_maxbot_plus_extension(ext, config_dict) if CONST_MAXBLOCK_EXTENSION_NAME in ext: dump_settings_to_maxblock_plus_extension(ext, config_dict) 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-blink-features=AutomationControlled") options.add_argument("--disable-infobars") options.add_argument("--disable-notifications") options.add_argument("--disable-popup-blocking") options.add_argument("--disable-print-preview") options.add_argument("--disable-setuid-sandbox") options.add_argument("--disable-site-isolation-trials") options.add_argument("--disable-smooth-scrolling") options.add_argument("--disable-sync") options.add_argument("--no-sandbox"); options.add_argument('--disable-features=TranslateUI') options.add_argument('--disable-translate') options.add_argument('--disable-web-security') options.add_argument('--lang=zh-TW') options.add_argument("--password-store=basic") options.add_experimental_option("prefs", {"credentials_enable_service": False, "profile.password_manager_enabled": False, "translate":{"enabled": False}}) 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 = 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 = 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 = clean_uc_exe_cache() 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) # 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("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 = 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 = ['*/adblock.js' ,'*/google_ad_block.js' ,'*google-analytics.*' ,'*googletagmanager.*' ,'*googletagservices.*' ,'*play.google.com/*' ,'*.googlesyndication.com/*' ,'*cdn.cookielaw.org/*' ,'*fundingchoicesmessages.google.com/*' ,'*.doubleclick.net/*' ,'*.rollbar.com/*' ,'*.cloudfront.com/*' ,'*.lndata.com/*' ,'*.twitter.com/i/*' ,'*platform.twitter.com/*' ,'*syndication.twitter.com/*' ,'*youtube.com/*' ,'*player.youku.*' ,'*.clarity.ms/*' ,'*img.uniicreative.com/*' ,'*e2elog.fetnet.net*'] 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/*') 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: 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 'ibon.com' in homepage: pass 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 'galaxymacau.com' in homepage: pass 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: if len(config_dict["advanced"]["tixcraft_sid"]) > 1: tixcraft_sid = decryptMe(config_dict["advanced"]["tixcraft_sid"]) driver.delete_cookie("SID") driver.add_cookie({"name":"SID", "value": tixcraft_sid, "path" : "/", "secure":True}) if 'ibon.com' in homepage: if len(config_dict["advanced"]["ibonqware"]) > 1: ibonqware = decryptMe(config_dict["advanced"]["ibonqware"]) 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 # common functions. def find_between( s, first, last ): ret = "" try: start = s.index( first ) + len( first ) end = s.index( last, start ) ret = s[start:end] except ValueError: pass return ret # convert web string to reg pattern def convert_string_to_pattern(my_str, dynamic_length=True): my_hint_anwser_length = len(my_str) my_formated = "" if my_hint_anwser_length > 0: my_anwser_symbols = "()[]<>{}-" for idx in range(my_hint_anwser_length): char = my_str[idx:idx+1] if char in my_anwser_symbols: my_formated += ('\\' + char) continue pattern = re.compile("[A-Z]") match_result = pattern.match(char) #print("match_result A:", match_result) if not match_result is None: my_formated += "[A-Z]" pattern = re.compile("[a-z]") match_result = pattern.match(char) #print("match_result a:", match_result) if not match_result is None: my_formated += "[a-z]" pattern = re.compile("[\d]") match_result = pattern.match(char) #print("match_result d:", match_result) if not match_result is None: my_formated += "[\d]" # for dynamic length if dynamic_length: for i in range(10): my_formated = my_formated.replace("[A-Z][A-Z]","[A-Z]") my_formated = my_formated.replace("[a-z][a-z]","[a-z]") my_formated = my_formated.replace("[\d][\d]","[\d]") my_formated = my_formated.replace("[A-Z]","[A-Z]+") my_formated = my_formated.replace("[a-z]","[a-z]+") my_formated = my_formated.replace("[\d]","[\d]+") return my_formated def guess_answer_list_from_multi_options(tmp_text): show_debug_message = True # debug. show_debug_message = False # online options_list = [] matched_pattern = "" if len(options_list) == 0: if '【' in tmp_text and '】' in tmp_text: pattern = '【.{1,4}】' options_list = re.findall(pattern, tmp_text) if len(options_list) <= 2: options_list = [] else: matched_pattern = pattern if len(options_list) == 0: if '(' in tmp_text and ')' in tmp_text: pattern = '\(.{1,4}\)' options_list = re.findall(pattern, tmp_text) if len(options_list) <= 2: options_list = [] else: matched_pattern = pattern if len(options_list) == 0: if '[' in tmp_text and ']' in tmp_text: pattern = '\[.{1,4}\]' options_list = re.findall(pattern, tmp_text) if len(options_list) <= 2: options_list = [] else: matched_pattern = pattern if len(options_list) == 0: if "\n" in tmp_text and ')' in tmp_text: pattern = "\\n.{1,4}\)" options_list = re.findall(pattern, tmp_text) if len(options_list) <= 2: options_list = [] else: matched_pattern = pattern if len(options_list) == 0: if "\n" in tmp_text and ']' in tmp_text: pattern = "\\n.{1,4}\]" options_list = re.findall(pattern, tmp_text) if len(options_list) <= 2: options_list = [] else: matched_pattern = pattern if len(options_list) == 0: if "\n" in tmp_text and '】' in tmp_text: pattern = "\\n.{1,4}】" options_list = re.findall(pattern, tmp_text) if len(options_list) <= 2: options_list = [] else: matched_pattern = pattern if len(options_list) == 0: if "\n" in tmp_text and ':' in tmp_text: pattern = "\\n.{1,4}:" options_list = re.findall(pattern, tmp_text) if len(options_list) <= 2: options_list = [] else: matched_pattern = pattern if len(options_list) == 0: if " " in tmp_text and '?' in tmp_text: if ('.' in tmp_text or ':' in tmp_text or ')' in tmp_text or ']' in tmp_text or '>' in tmp_text): pattern = "[ /\n\|;\.\?]{1}.{1}[\.:)\]>]{1}.{2,3}" options_list = re.findall(pattern, tmp_text) if len(options_list) <= 2: options_list = [] else: formated_list = [] for new_item in options_list: new_item = new_item.strip() if new_item[:1] == ".": new_item = new_item[1:] if new_item[:1] == "?": new_item = new_item[1:] if new_item[:1] == "|": new_item = new_item[1:] if new_item[:1] == ";": new_item = new_item[1:] if new_item[:1] == "/": new_item = new_item[1:] new_item = new_item.strip() new_item = new_item[:1] formated_list.append(new_item) options_list = formated_list matched_pattern = pattern if show_debug_message: print("matched pattern:", matched_pattern) # default remove quota is_trim_quota = not check_answer_keep_symbol(tmp_text) if show_debug_message: print("is_trim_quota:", is_trim_quota) return_list = [] if len(options_list) > 0: options_list_length = len(options_list) if show_debug_message: print("options_list_length:", options_list_length) print("options_list:", options_list) if options_list_length > 2: is_all_options_same_length = True options_length_count = {} for i in range(options_list_length-1): current_option_length = len(options_list[i]) next_option_length = len(options_list[i+1]) if current_option_length != next_option_length: is_all_options_same_length = False if current_option_length in options_length_count: options_length_count[current_option_length] += 1 else: options_length_count[current_option_length] = 1 if show_debug_message: print("is_all_options_same_length:", is_all_options_same_length) if is_all_options_same_length: return_list = [] for each_option in options_list: if len(each_option) > 2: if is_trim_quota: return_list.append(each_option[1:-1]) else: return_list.append(each_option) else: return_list.append(each_option) else: #print("options_length_count:", options_length_count) if len(options_length_count) > 0: target_option_length = 0 most_length_count = 0 for k in options_length_count.keys(): if options_length_count[k] > most_length_count: most_length_count = options_length_count[k] target_option_length = k #print("most_length_count:", most_length_count) #print("target_option_length:", target_option_length) if target_option_length > 0: return_list = [] for each_option in options_list: current_option_length = len(each_option) if current_option_length == target_option_length: if is_trim_quota: return_list.append(each_option[1:-1]) else: return_list.append(each_option) # something is wrong, give up when option equal 2 options. if len(return_list) <= 2: return_list = [] # remove chinese work options. if len(options_list) > 0: new_list = [] for item in return_list: if is_all_alpha_or_numeric(item): new_list.append(item) if len(new_list) >=3: return_list = new_list return return_list #PS: this may get a wrong answer list. XD def guess_answer_list_from_symbols(captcha_text_div_text): return_list = [] # need replace to space to get first options. tmp_text = captcha_text_div_text tmp_text = tmp_text.replace('?',' ') tmp_text = tmp_text.replace('?',' ') tmp_text = tmp_text.replace('。',' ') delimitor_symbols_left = [u"(","[","{", " ", " ", " ", " "] delimitor_symbols_right = [u")","]","}", ":", ".", ")", "-"] idx = -1 for idx in range(len(delimitor_symbols_left)): symbol_left = delimitor_symbols_left[idx] symbol_right = delimitor_symbols_right[idx] if symbol_left in tmp_text and symbol_right in tmp_text and '半形' in tmp_text: hint_list = re.findall('\\'+ symbol_left + '[\\w]+\\'+ symbol_right , tmp_text) #print("hint_list:", hint_list) if not hint_list is None: if len(hint_list) > 1: return_list = [] my_answer_delimitor = symbol_right for options in hint_list: if len(options) > 2: my_anwser = options[1:-1] #print("my_anwser:",my_anwser) if len(my_anwser) > 0: return_list.append(my_anwser) if len(return_list) > 0: break return return_list def get_offical_hint_string_from_symbol(symbol, tmp_text): show_debug_message = True # debug. show_debug_message = False # online offical_hint_string = "" if symbol in tmp_text: # start to guess offical hint if offical_hint_string == "": if '【' in tmp_text and '】' in tmp_text: hint_list = re.findall('【.*?】', tmp_text) if not hint_list is None: if show_debug_message: print("【.*?】hint_list:", hint_list) for hint in hint_list: if symbol in hint: offical_hint_string = hint[1:-1] break if offical_hint_string == "": if '(' in tmp_text and ')' in tmp_text: hint_list = re.findall('\(.*?\)', tmp_text) if not hint_list is None: if show_debug_message: print("\(.*?\)hint_list:", hint_list) for hint in hint_list: if symbol in hint: offical_hint_string = hint[1:-1] break if offical_hint_string == "": if '[' in tmp_text and ']' in tmp_text: hint_list = re.findall('[.*?]', tmp_text) if not hint_list is None: if show_debug_message: print("[.*?]hint_list:", hint_list) for hint in hint_list: if symbol in hint: offical_hint_string = hint[1:-1] break if offical_hint_string == "": offical_hint_string = tmp_text return offical_hint_string def guess_answer_list_from_hint(CONST_EXAMPLE_SYMBOL, CONST_INPUT_SYMBOL, captcha_text_div_text): show_debug_message = True # debug. show_debug_message = False # online tmp_text = format_question_string(CONST_EXAMPLE_SYMBOL, CONST_INPUT_SYMBOL, captcha_text_div_text) my_question = "" my_options = "" offical_hint_string = "" offical_hint_string_anwser = "" my_anwser_formated = "" my_answer_delimitor = "" if my_question == "": if "?" in tmp_text: question_index = tmp_text.find("?") my_question = tmp_text[:question_index+1] if my_question == "": if "。" in tmp_text: question_index = tmp_text.find("。") my_question = tmp_text[:question_index+1] if my_question == "": my_question = tmp_text #print("my_question:", my_question) # ps: hint_list is not options list if offical_hint_string == "": # for: 若你覺得答案為 a,請輸入 a if '答案' in tmp_text and CONST_INPUT_SYMBOL in tmp_text: offical_hint_string = get_offical_hint_string_from_symbol(CONST_INPUT_SYMBOL, tmp_text) if len(offical_hint_string) > 0: right_part = offical_hint_string.split(CONST_INPUT_SYMBOL)[1] #print("right_part:", right_part) if len(offical_hint_string) == len(tmp_text): offical_hint_string = right_part new_hint = find_continuous_text(right_part) if len(new_hint) > 0: # TODO: 答案為B需填入Bb) #if '答案' in offical_hint_string and CONST_INPUT_SYMBOL in offical_hint_string: offical_hint_string_anwser = new_hint if offical_hint_string == "": offical_hint_string = get_offical_hint_string_from_symbol(CONST_EXAMPLE_SYMBOL, tmp_text) if len(offical_hint_string) > 0: right_part = offical_hint_string.split(CONST_EXAMPLE_SYMBOL)[1] if len(offical_hint_string) == len(tmp_text): offical_hint_string = right_part # PS: find first text will only get B char in this case: 答案為B需填入Bb) new_hint = find_continuous_text(right_part) if len(new_hint) > 0: offical_hint_string_anwser = new_hint # resize offical_hint_string_anwser for options contains in hint string. #print("offical_hint_string_anwser:", offical_hint_string_anwser) if len(offical_hint_string_anwser) > 0: offical_hint_string = offical_hint_string.split(offical_hint_string_anwser)[0] if show_debug_message: print("offical_hint_string:", offical_hint_string) # try rule4: # get hint from rule 3: without '(' & '), but use "*" if len(offical_hint_string) == 0: target_symbol = "*" if target_symbol in tmp_text : star_index = tmp_text.find(target_symbol) space_index = tmp_text.find(" ", star_index + len(target_symbol)) offical_hint_string = tmp_text[star_index: space_index] # is need to merge next block if len(offical_hint_string) > 0: target_symbol = offical_hint_string + " " if target_symbol in tmp_text : star_index = tmp_text.find(target_symbol) next_block_index = star_index + len(target_symbol) space_index = tmp_text.find(" ", next_block_index) next_block = tmp_text[next_block_index: space_index] if CONST_EXAMPLE_SYMBOL in next_block: offical_hint_string += ' ' + next_block # try rule5: # get hint from rule 3: n個半形英文大寫 if len(offical_hint_string) == 0: target_symbol = "個半形英文大寫" if target_symbol in tmp_text : star_index = tmp_text.find(target_symbol) space_index = tmp_text.find(" ", star_index) answer_char_count = tmp_text[star_index-1:star_index] if answer_char_count.isnumeric(): answer_char_count = chinese_numeric_to_int(answer_char_count) if answer_char_count is None: answer_char_count = '0' star_index -= 1 offical_hint_string_anwser = 'A' * int(answer_char_count) offical_hint_string = tmp_text[star_index: space_index] target_symbol = "個英文大寫" if target_symbol in tmp_text : star_index = tmp_text.find(target_symbol) space_index = tmp_text.find(" ", star_index) answer_char_count = tmp_text[star_index-1:star_index] if answer_char_count.isnumeric(): answer_char_count = chinese_numeric_to_int(answer_char_count) if answer_char_count is None: answer_char_count = '0' star_index -= 1 offical_hint_string_anwser = 'A' * int(answer_char_count) offical_hint_string = tmp_text[star_index: space_index] target_symbol = "個半形英文小寫" if target_symbol in tmp_text : star_index = tmp_text.find(target_symbol) space_index = tmp_text.find(" ", star_index) answer_char_count = tmp_text[star_index-1:star_index] if answer_char_count.isnumeric(): answer_char_count = chinese_numeric_to_int(answer_char_count) if answer_char_count is None: answer_char_count = '0' star_index -= 1 offical_hint_string_anwser = 'a' * int(answer_char_count) offical_hint_string = tmp_text[star_index: space_index] target_symbol = "個英文小寫" if target_symbol in tmp_text : star_index = tmp_text.find(target_symbol) space_index = tmp_text.find(" ", star_index) answer_char_count = tmp_text[star_index-1:star_index] if answer_char_count.isnumeric(): answer_char_count = chinese_numeric_to_int(answer_char_count) if answer_char_count is None: answer_char_count = '0' star_index -= 1 offical_hint_string_anwser = 'a' * int(answer_char_count) offical_hint_string = tmp_text[star_index: space_index] target_symbol = "個英數半形字" if target_symbol in tmp_text : star_index = tmp_text.find(target_symbol) space_index = tmp_text.find(" ", star_index) answer_char_count = tmp_text[star_index-1:star_index] if answer_char_count.isnumeric(): answer_char_count = chinese_numeric_to_int(answer_char_count) if answer_char_count is None: answer_char_count = '0' star_index -= 1 my_anwser_formated = '[A-Za-z\d]' * int(answer_char_count) offical_hint_string = tmp_text[star_index: space_index] target_symbol = "個半形" if target_symbol in tmp_text : star_index = tmp_text.find(target_symbol) space_index = tmp_text.find(" ", star_index) answer_char_count = tmp_text[star_index-1:star_index] if answer_char_count.isnumeric(): answer_char_count = chinese_numeric_to_int(answer_char_count) if answer_char_count is None: answer_char_count = '0' star_index -= 1 my_anwser_formated = '[A-Za-z\d]' * int(answer_char_count) offical_hint_string = tmp_text[star_index: space_index] if len(offical_hint_string) > 0: if show_debug_message: print("offical_hint_string_anwser:", offical_hint_string_anwser) my_anwser_formated = convert_string_to_pattern(offical_hint_string_anwser) my_options = tmp_text if len(my_question) < len(tmp_text): my_options = my_options.replace(my_question,"") my_options = my_options.replace(offical_hint_string,"") # try rule7: # check is chinese/english in question, if match, apply my_options rule. if len(offical_hint_string) > 0: tmp_text_org = captcha_text_div_text if CONST_EXAMPLE_SYMBOL in tmp_text: tmp_text_org = tmp_text_org.replace('Ex:','ex:') target_symbol = "ex:" if target_symbol in tmp_text_org : star_index = tmp_text_org.find(target_symbol) my_options = tmp_text_org[star_index-1:] if show_debug_message: print("tmp_text:", tmp_text) print("my_options:", my_options) if len(my_anwser_formated) > 0: allow_delimitor_symbols = ")].: }" pattern = re.compile(my_anwser_formated) search_result = pattern.search(my_options) if not search_result is None: (span_start, span_end) = search_result.span() maybe_delimitor="" if len(my_options) > (span_end+1)+1: maybe_delimitor = my_options[span_end+0:span_end+1] if maybe_delimitor in allow_delimitor_symbols: my_answer_delimitor = maybe_delimitor if show_debug_message: print("my_answer_delimitor:", my_answer_delimitor) # default remove quota is_trim_quota = not check_answer_keep_symbol(tmp_text) if show_debug_message: print("is_trim_quota:", is_trim_quota) return_list = [] if len(my_anwser_formated) > 0: new_pattern = my_anwser_formated if len(my_answer_delimitor) > 0: new_pattern = my_anwser_formated + '\\' + my_answer_delimitor return_list = re.findall(new_pattern, my_options) if show_debug_message: print("my_anwser_formated:", my_anwser_formated) print("new_pattern:", new_pattern) print("return_list:" , return_list) if not return_list is None: if len(return_list) == 1: # re-sample for this case. return_list = re.findall(my_anwser_formated, my_options) if len(return_list) == 1: # if use pattern to find matched only one, means it is for example text. return_list = None if not return_list is None: # clean delimitor if is_trim_quota: return_list_length = len(return_list) if return_list_length >= 1: if len(my_answer_delimitor) > 0: for idx in range(return_list_length): return_list[idx]=return_list[idx].replace(my_answer_delimitor,'') if show_debug_message: print("cleaned return_list:" , return_list) if return_list is None: return_list = [] return return_list, offical_hint_string_anwser def format_question_string(CONST_EXAMPLE_SYMBOL, CONST_INPUT_SYMBOL, captcha_text_div_text): tmp_text = captcha_text_div_text tmp_text = tmp_text.replace(' ',' ') tmp_text = tmp_text.replace(':',':') # for hint tmp_text = tmp_text.replace('*','*') # stop word. tmp_text = tmp_text.replace('輸入法','') tmp_text = tmp_text.replace('請問','') tmp_text = tmp_text.replace('請將','') tmp_text = tmp_text.replace('請在','') tmp_text = tmp_text.replace('請以','') tmp_text = tmp_text.replace('請回答','') tmp_text = tmp_text.replace('請','') # replace ex. tmp_text = tmp_text.replace('例如', CONST_EXAMPLE_SYMBOL) tmp_text = tmp_text.replace('如:', CONST_EXAMPLE_SYMBOL) tmp_text = tmp_text.replace('如為', CONST_EXAMPLE_SYMBOL+'為') tmp_text = tmp_text.replace('舉例', CONST_EXAMPLE_SYMBOL) if not CONST_EXAMPLE_SYMBOL in tmp_text: tmp_text = tmp_text.replace('例', CONST_EXAMPLE_SYMBOL) # important, maybe 例 & ex occurs at same time. tmp_text = tmp_text.replace('ex:', CONST_EXAMPLE_SYMBOL) tmp_text = tmp_text.replace('Ex:', CONST_EXAMPLE_SYMBOL) #若你覺得 #PS:這個,可能會造成更多問題,呵呵。 SYMBOL_IF_LIST = ['假設','如果','若'] for symbol_if in SYMBOL_IF_LIST: if symbol_if in tmp_text and '答案' in tmp_text: tmp_text = tmp_text.replace('覺得', '') tmp_text = tmp_text.replace('認為', '') tmp_text = tmp_text.replace(symbol_if + '你答案', CONST_EXAMPLE_SYMBOL + '答案') tmp_text = tmp_text.replace(symbol_if + '答案', CONST_EXAMPLE_SYMBOL + '答案') tmp_text = tmp_text.replace('填入', CONST_INPUT_SYMBOL) #tmp_text = tmp_text.replace('[','(') #tmp_text = tmp_text.replace(']',')') tmp_text = tmp_text.replace('?','?') tmp_text = tmp_text.replace('(','(') tmp_text = tmp_text.replace(')',')') return tmp_text def permutations(iterable, r=None): pool = tuple(iterable) n = len(pool) r = n if r is None else r if r > n: return indices = list(range(n)) cycles = list(range(n, n-r, -1)) yield tuple(pool[i] for i in indices[:r]) while n: for i in reversed(range(r)): cycles[i] -= 1 if cycles[i] == 0: indices[i:] = indices[i+1:] + indices[i:i+1] cycles[i] = n - i else: j = cycles[i] indices[i], indices[-j] = indices[-j], indices[i] yield tuple(pool[i] for i in indices[:r]) break else: return def get_answer_list_by_question(CONST_EXAMPLE_SYMBOL, CONST_INPUT_SYMBOL, captcha_text_div_text): show_debug_message = True # debug. show_debug_message = False # online return_list = [] tmp_text = format_question_string(CONST_EXAMPLE_SYMBOL, CONST_INPUT_SYMBOL, captcha_text_div_text) # guess answer list from multi-options: 【】() [] if len(return_list)==0: return_list = guess_answer_list_from_multi_options(tmp_text) if show_debug_message: print("captcha_text_div_text:", captcha_text_div_text) if len(return_list) > 0: print("found, guess_answer_list_from_multi_options:", return_list) offical_hint_string_anwser = "" if len(return_list)==0: return_list, offical_hint_string_anwser = guess_answer_list_from_hint(CONST_EXAMPLE_SYMBOL, CONST_INPUT_SYMBOL, captcha_text_div_text) else: is_match_factorial = False mutiple = 0 return_list_2, offical_hint_string_anwser = guess_answer_list_from_hint(CONST_EXAMPLE_SYMBOL, CONST_INPUT_SYMBOL, captcha_text_div_text) if return_list_2 is None: if len(offical_hint_string_anwser) >=3: if len(return_list) >=3: mutiple = int(len(offical_hint_string_anwser) / len(return_list[0])) if mutiple >=3 : is_match_factorial = True if show_debug_message: print("mutiple:", mutiple) print("is_match_factorial:", is_match_factorial) if is_match_factorial: is_match_factorial = False order_string_list = ['排列','排序','依序','順序','遞增','遞減','升冪','降冪','新到舊','舊到新','小到大','大到小','高到低','低到高'] for order_string in order_string_list: if order_string in tmp_text: is_match_factorial = True if is_match_factorial: new_array = permutations(return_list, mutiple) #print("new_array:", new_array) return_list = [] for item_tuple in new_array: return_list.append(''.join(item_tuple)) if show_debug_message: if len(return_list) > 0: print("found, guess_answer_list_from_hint:", return_list) if len(return_list)==0: return_list = guess_answer_list_from_symbols(captcha_text_div_text) if show_debug_message: if len(return_list) > 0: print("found, guess_answer_list_from_symbols:", return_list) return return_list 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 = force_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 force_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, config_dict): show_debug_message = True # debug. show_debug_message = False # online accept_all_cookies_btn = None try: accept_all_cookies_btn = driver.find_element(By.CSS_SELECTOR, '#onetrust-accept-btn-handler') except Exception as exc: #print(exc) if show_debug_message: print("find accept_all_cookies_btn fail") pass if not accept_all_cookies_btn is None: is_visible = False try: if accept_all_cookies_btn.is_enabled() and accept_all_cookies_btn.is_displayed(): is_visible = True except Exception as exc: #print(exc) pass if is_visible: if show_debug_message: print("accept_all_cookies_btn visible. start to press.") try: accept_all_cookies_btn.click() except Exception as exc: #print(exc) print("try to click accept_all_cookies_btn fail, force click by js.") try: driver.execute_script("arguments[0].click();", accept_all_cookies_btn) except Exception as exc: pass else: if show_debug_message: print("accept_all_cookies_btn invisible.") # from detail to game def tixcraft_redirect(driver, url): ret = False game_name = "" # get game_name from url url_split = url.split("/") if len(url_split) >= 6: game_name = url_split[5] if "/activity/detail/%s" % (game_name,) in url: # to support teamear entry_url = url.replace("/activity/detail/","/activity/game/") print("redirec to new url:", entry_url) try: driver.get(entry_url) except Exception as exec1: pass ret = True return ret def get_target_item_from_matched_list(matched_blocks, auto_select_mode): target_area = None if not matched_blocks is None: matched_blocks_count = len(matched_blocks) if matched_blocks_count > 0: target_row_index = 0 if auto_select_mode == CONST_FROM_TOP_TO_BOTTOM: pass if auto_select_mode == CONST_FROM_BOTTOM_TO_TOP: target_row_index = matched_blocks_count - 1 if auto_select_mode == CONST_RANDOM: if matched_blocks_count > 1: target_row_index = random.randint(0,matched_blocks_count-1) if auto_select_mode == CONST_CENTER: if matched_blocks_count > 2: target_row_index = int(matched_blocks_count/2) target_area = matched_blocks[target_row_index] return target_area 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 html_text = remove_html_tags(html_body) if not html_text is None: 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 ' 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 = 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 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 = 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 = 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 = force_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 = force_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 = 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 = format_keyword_string(date_keyword) if show_debug_message: print("start to match formated keyword:", date_keyword) matched_blocks = 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 = get_target_item_from_matched_list(matched_blocks, auto_select_mode) is_date_clicked = False if not target_area is None: is_date_clicked = force_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 def get_matched_blocks_by_keyword_item_set(config_dict, auto_select_mode, keyword_item_set, formated_area_list): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True 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 = 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 reset_row_text_if_match_keyword_exclude(config_dict, row_text): row_text = "" if len(row_text) > 0: # start to compare, normalize all. row_text = format_keyword_string(row_text) if show_debug_message: print("row_text:", row_text) is_match_all = False if ' ' in keyword_item_set: keyword_item_array = keyword_item_set.split(' ') is_match_all = True for keyword_item in keyword_item_array: keyword_item = format_keyword_string(keyword_item) if not keyword_item in row_text: is_match_all = False else: exclude_item = format_keyword_string(keyword_item_set) if exclude_item in row_text: is_match_all = True if is_match_all: matched_blocks.append(row) # only need first row. if auto_select_mode == CONST_FROM_TOP_TO_BOTTOM: break return matched_blocks def get_matched_blocks_by_keyword(config_dict, auto_select_mode, keyword_string, formated_area_list): keyword_array = [] try: keyword_array = json.loads("["+ keyword_string +"]") except Exception as exc: keyword_array = [] matched_blocks = [] for keyword_item_set in keyword_array: matched_blocks = get_matched_blocks_by_keyword_item_set(config_dict, auto_select_mode, keyword_item_set, formated_area_list) if len(matched_blocks) > 0: break return matched_blocks def is_row_match_keyword(keyword_string, row_text): # clean stop word. row_text = format_keyword_string(row_text) is_match_keyword = True if len(keyword_string) > 0 and len(row_text) > 0: is_match_keyword = False keyword_array = [] try: keyword_array = json.loads("["+ keyword_string +"]") except Exception as exc: keyword_array = [] for item_list in keyword_array: if len(item_list) > 0: if ' ' in item_list: keyword_item_array = item_list.split(' ') is_match_all_exclude = True for each_item in keyword_item_array: each_item = format_keyword_string(each_item) if not each_item in row_text: is_match_all_exclude = False if is_match_all_exclude: is_match_keyword = True else: item_list = format_keyword_string(item_list) if item_list in row_text: is_match_keyword = True else: # match all. is_match_keyword = True if is_match_keyword: break return is_match_keyword def reset_row_text_if_match_keyword_exclude(config_dict, row_text): area_keyword_exclude = config_dict["keyword_exclude"] return is_row_match_keyword(area_keyword_exclude, row_text) # 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 = 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 reset_row_text_if_match_keyword_exclude(config_dict, row_text): row_text = "" if len(row_text) > 0: # clean stop word. row_text = 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 = 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 reset_row_text_if_match_keyword_exclude(config_dict, row_text): row_text = "" if len(row_text) > 0: # clean stop word. row_text = 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 = 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 = 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 = 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 tixcraft_ticket_agree(driver, config_dict): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True agree_checkbox = None try: my_css_selector = '#TicketForm_agree' agree_checkbox = driver.find_element(By.CSS_SELECTOR, my_css_selector) except Exception as exc: print("find TicketForm_agree fail") if show_debug_message: print(exc) pass is_finish_checkbox_click = force_check_checkbox(driver, agree_checkbox) return is_finish_checkbox_click 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_div_text_by_selector(driver, my_css_selector): div_element = None try: div_element = driver.find_element(By.CSS_SELECTOR, my_css_selector) except Exception as exc: print("find verify textbox fail") pass question_text = "" if not div_element is None: try: question_text = div_element.text except Exception as exc: print("get text fail") if question_text is None: question_text = "" return question_text def guess_tixcraft_question(driver, question_text): show_debug_message = True # debug. show_debug_message = False # online answer_list = [] formated_html_text = "" if len(question_text) > 0: # format question text. formated_html_text = question_text formated_html_text = format_quota_string(formated_html_text) if '【' in formated_html_text and '】' in formated_html_text: # PS: 這個太容易沖突,因為問題類型太多,不能直接使用。 #inferred_answer_string = find_between(formated_html_text, "【", "】") pass if show_debug_message: print("formated_html_text:", formated_html_text) # start to guess answer inferred_answer_string = None # 請輸入"YES",代表您已詳閱且瞭解並同意。 if inferred_answer_string is None: if '輸入"YES"' in formated_html_text: if '已詳閱' in formated_html_text or '請詳閱' in formated_html_text: if '同意' in formated_html_text: inferred_answer_string = 'YES' # 購票前請詳閱注意事項,並於驗證碼欄位輸入【同意】繼續購票流程。 if inferred_answer_string is None: if '驗證碼' in formated_html_text or '驗證欄位' in formated_html_text: if '已詳閱' in formated_html_text or '請詳閱' in formated_html_text: if '輸入【同意】' in formated_html_text: inferred_answer_string = '同意' if inferred_answer_string is None: if len(question_text) > 0: answer_list = get_answer_list_from_question_string(None, question_text) else: answer_list = [answer_list] return answer_list 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 = get_answer_list_from_user_guess_string(config_dict) 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 if len(next_step_button_css) > 0: is_button_clicked = force_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 = force_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 get_answer_list_from_user_guess_string(config_dict): local_array = [] online_array = [] user_guess_string = config_dict["advanced"]["user_guess_string"] if len(user_guess_string) > 0: user_guess_string = format_config_keyword_for_json(user_guess_string) try: local_array = json.loads("["+ user_guess_string +"]") except Exception as exc: local_array = [] # load from internet. user_guess_string = "" if os.path.exists(CONST_MAXBOT_ANSWER_ONLINE_FILE): with open(CONST_MAXBOT_ANSWER_ONLINE_FILE, "r") as text_file: user_guess_string = text_file.readline() if len(user_guess_string) > 0: user_guess_string = format_config_keyword_for_json(user_guess_string) try: online_array = json.loads("["+ user_guess_string +"]") except Exception as exc: online_array = [] return local_array + online_array 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_div_text_by_selector(driver, question_selector) if len(question_text) > 0: write_question_to_file(question_text) answer_list = get_answer_list_from_user_guess_string(config_dict) if len(answer_list)==0: if config_dict["advanced"]["auto_guess_options"]: answer_list = 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): for i in range(3): is_finish_checkbox_click = tixcraft_ticket_agree(driver, config_dict) if is_finish_checkbox_click: break 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 = 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 reset_row_text_if_match_keyword_exclude(config_dict, row_text): row_text = "" if len(row_text) > 0: # clean stop word. row_text = 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 = 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 = 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 = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True 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 = 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_get_web_datetime(registrationsNewApp_div): show_debug_message = True # debug. show_debug_message = False # online web_datetime = None is_found_web_datetime = False el_web_datetime_list = None if not registrationsNewApp_div is None: try: el_web_datetime_list = registrationsNewApp_div.find_elements(By.TAG_NAME, 'td') except Exception as exc: if show_debug_message: print("find td.ng-binding Exception") print(exc) pass #print("is_found_web_datetime", is_found_web_datetime) if not el_web_datetime_list is None: el_web_datetime_list_count = len(el_web_datetime_list) if el_web_datetime_list_count > 0: el_web_datetime = None for el_web_datetime in el_web_datetime_list: el_web_datetime_text = None try: el_web_datetime_text = el_web_datetime.text if show_debug_message: print("el_web_datetime_text:", el_web_datetime_text) except Exception as exc: if show_debug_message: print('parse web datetime fail:') print(exc) pass if not el_web_datetime_text is None: if len(el_web_datetime_text) > 0: now = datetime.now() #print("now:", now) for guess_year in range(now.year,now.year+3): current_year = str(guess_year) if current_year in el_web_datetime_text: if '/' in el_web_datetime_text: web_datetime = el_web_datetime_text is_found_web_datetime = True break if is_found_web_datetime: break else: print("find td.ng-binding fail") if show_debug_message: print('is_found_web_datetime:', is_found_web_datetime) print('web_datetime:', web_datetime) return web_datetime def kktix_check_agree_checkbox(driver, config_dict): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True is_finish_checkbox_click = False agree_label = None agree_checkbox = None try: agree_label = driver.find_element(By.CSS_SELECTOR, 'label[for="person_agree_terms"]') agree_checkbox = driver.find_element(By.CSS_SELECTOR, '#person_agree_terms') except Exception as exc: print("find person_agree_terms checkbox Exception") if show_debug_message: print(exc) pass is_dom_ready = False is_need_refresh = False if not agree_checkbox is None and not agree_label is None: checkbox_html = "" try: checkbox_html = agree_label.get_attribute('innerHTML').strip() #print("agree_checkbox html:", checkbox_html) if len(checkbox_html) > 0: if not "{{'new.i_read_and_agree_to'" in checkbox_html: is_dom_ready = True except Exception as e: #print(e) pass is_finish_checkbox_click = force_check_checkbox(driver, agree_checkbox) #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 def get_answer_string_from_web_date(CONST_EXAMPLE_SYMBOL, CONST_INPUT_SYMBOL, registrationsNewApp_div, captcha_text_div_text): show_debug_message = True # debug. show_debug_message = False # online inferred_answer_string = None is_need_parse_web_datetime = False # '半形阿拉伯數字' & '半形數字' if '半形' in captcha_text_div_text and '字' in captcha_text_div_text: if '演出日期' in captcha_text_div_text: is_need_parse_web_datetime = True if '活動日期' in captcha_text_div_text: is_need_parse_web_datetime = True if '表演日期' in captcha_text_div_text: is_need_parse_web_datetime = True if '開始日期' in captcha_text_div_text: is_need_parse_web_datetime = True if '演唱會日期' in captcha_text_div_text: is_need_parse_web_datetime = True if '展覽日期' in captcha_text_div_text: is_need_parse_web_datetime = True if '音樂會日期' in captcha_text_div_text: is_need_parse_web_datetime = True if 'the date of the show you purchased' in captcha_text_div_text: is_need_parse_web_datetime = True if show_debug_message: print("is_need_parse_web_datetime:", is_need_parse_web_datetime) if is_need_parse_web_datetime: web_datetime = kktix_get_web_datetime(registrationsNewApp_div) if not web_datetime is None: if show_debug_message: print("web_datetime:", web_datetime) captcha_text_formatted = format_question_string(CONST_EXAMPLE_SYMBOL, CONST_INPUT_SYMBOL, captcha_text_div_text) if show_debug_message: print("captcha_text_formatted", captcha_text_formatted) my_datetime_foramted = None # MMDD if my_datetime_foramted is None: if '4位半形' in captcha_text_formatted: my_datetime_foramted = "%m%d" # for "如為2月30日,請輸入0230" if my_datetime_foramted is None: right_part = "" if CONST_EXAMPLE_SYMBOL in captcha_text_formatted: right_part = captcha_text_formatted.split(CONST_EXAMPLE_SYMBOL)[1] if CONST_INPUT_SYMBOL in right_part: right_part = right_part.split(CONST_INPUT_SYMBOL)[1] number_text = find_continuous_number(right_part) my_anwser_formated = convert_string_to_pattern(number_text, dynamic_length=False) if my_anwser_formated == "[\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d]": my_datetime_foramted = "%Y%m%d" if my_anwser_formated == "[\\d][\\d][\\d][\\d]": my_datetime_foramted = "%m%d" #print("my_datetime_foramted:", my_datetime_foramted) if show_debug_message: print("my_datetime_foramted", my_datetime_foramted) if my_datetime_foramted is None: now = datetime.now() for guess_year in range(now.year-4,now.year+2): current_year = str(guess_year) if current_year in captcha_text_formatted: my_hint_index = captcha_text_formatted.find(current_year) my_hint_anwser = captcha_text_formatted[my_hint_index:] #print("my_hint_anwser:", my_hint_anwser) # get after. my_delimitor_symbol = CONST_EXAMPLE_SYMBOL if my_delimitor_symbol in my_hint_anwser: my_delimitor_index = my_hint_anwser.find(my_delimitor_symbol) my_hint_anwser = my_hint_anwser[my_delimitor_index+len(my_delimitor_symbol):] #print("my_hint_anwser:", my_hint_anwser) # get before. my_delimitor_symbol = ',' if my_delimitor_symbol in my_hint_anwser: my_delimitor_index = my_hint_anwser.find(my_delimitor_symbol) my_hint_anwser = my_hint_anwser[:my_delimitor_index] my_delimitor_symbol = '。' if my_delimitor_symbol in my_hint_anwser: my_delimitor_index = my_hint_anwser.find(my_delimitor_symbol) my_hint_anwser = my_hint_anwser[:my_delimitor_index] # PS: space may not is delimitor... my_delimitor_symbol = ' ' if my_delimitor_symbol in my_hint_anwser: my_delimitor_index = my_hint_anwser.find(my_delimitor_symbol) my_hint_anwser = my_hint_anwser[:my_delimitor_index] #remove last char. remove_last_char_list = [')','(','.','。',')','(','[',']'] for check_char in remove_last_char_list: if my_hint_anwser[-1:]==check_char: my_hint_anwser = my_hint_anwser[:-1] my_anwser_formated = convert_string_to_pattern(my_hint_anwser, dynamic_length=False) if my_anwser_formated == "[\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d]": my_datetime_foramted = "%Y%m%d" if my_anwser_formated == "[\\d][\\d][\\d][\\d]/[\\d][\\d]/[\\d][\\d]": my_datetime_foramted = "%Y/%m/%d" if show_debug_message: print("my_hint_anwser:", my_hint_anwser) print("my_anwser_formated:", my_anwser_formated) print("my_datetime_foramted:", my_datetime_foramted) break if not my_datetime_foramted is None: my_delimitor_symbol = ' ' if my_delimitor_symbol in web_datetime: web_datetime = web_datetime[:web_datetime.find(my_delimitor_symbol)] date_time = datetime.strptime(web_datetime,"%Y/%m/%d") if show_debug_message: print("our web date_time:", date_time) ans = None try: if not date_time is None: ans = date_time.strftime(my_datetime_foramted) except Exception as exc: pass inferred_answer_string = ans if show_debug_message: print("web date_time anwser:", ans) return inferred_answer_string def get_answer_string_from_web_time(CONST_EXAMPLE_SYMBOL, CONST_INPUT_SYMBOL, registrationsNewApp_div, captcha_text_div_text): show_debug_message = True # debug. show_debug_message = False # online inferred_answer_string = None # parse '演出時間' is_need_parse_web_time = False if '半形' in captcha_text_div_text: if '演出時間' in captcha_text_div_text: is_need_parse_web_time = True if '表演時間' in captcha_text_div_text: is_need_parse_web_time = True if '開始時間' in captcha_text_div_text: is_need_parse_web_time = True if '演唱會時間' in captcha_text_div_text: is_need_parse_web_time = True if '展覽時間' in captcha_text_div_text: is_need_parse_web_time = True if '音樂會時間' in captcha_text_div_text: is_need_parse_web_time = True if 'the time of the show you purchased' in captcha_text_div_text: is_need_parse_web_time = True #print("is_need_parse_web_time", is_need_parse_web_time) if is_need_parse_web_time: web_datetime = None if not registrationsNewApp_div is None: web_datetime = kktix_get_web_datetime(registrationsNewApp_div) if not web_datetime is None: tmp_text = format_question_string(CONST_EXAMPLE_SYMBOL, CONST_INPUT_SYMBOL, captcha_text_div_text) my_datetime_foramted = None if my_datetime_foramted is None: my_hint_anwser = tmp_text my_delimitor_symbol = CONST_EXAMPLE_SYMBOL if my_delimitor_symbol in my_hint_anwser: my_delimitor_index = my_hint_anwser.find(my_delimitor_symbol) my_hint_anwser = my_hint_anwser[my_delimitor_index+len(my_delimitor_symbol):] #print("my_hint_anwser:", my_hint_anwser) # get before. my_delimitor_symbol = ',' if my_delimitor_symbol in my_hint_anwser: my_delimitor_index = my_hint_anwser.find(my_delimitor_symbol) my_hint_anwser = my_hint_anwser[:my_delimitor_index] my_delimitor_symbol = '。' if my_delimitor_symbol in my_hint_anwser: my_delimitor_index = my_hint_anwser.find(my_delimitor_symbol) my_hint_anwser = my_hint_anwser[:my_delimitor_index] # PS: space may not is delimitor... my_delimitor_symbol = ' ' if my_delimitor_symbol in my_hint_anwser: my_delimitor_index = my_hint_anwser.find(my_delimitor_symbol) my_hint_anwser = my_hint_anwser[:my_delimitor_index] my_anwser_formated = convert_string_to_pattern(my_hint_anwser, dynamic_length=False) #print("my_hint_anwser:", my_hint_anwser) #print("my_anwser_formated:", my_anwser_formated) if my_anwser_formated == "[\\d][\\d][\\d][\\d]": my_datetime_foramted = "%H%M" if '12小時' in tmp_text: my_datetime_foramted = "%I%M" if my_anwser_formated == "[\\d][\\d]:[\\d][\\d]": my_datetime_foramted = "%H:%M" if '12小時' in tmp_text: my_datetime_foramted = "%I:%M" if not my_datetime_foramted is None: date_delimitor_symbol = '(' if date_delimitor_symbol in web_datetime: date_delimitor_symbol_index = web_datetime.find(date_delimitor_symbol) if date_delimitor_symbol_index > 8: web_datetime = web_datetime[:date_delimitor_symbol_index-1] date_time = datetime.strptime(web_datetime,"%Y/%m/%d %H:%M") #print("date_time:", date_time) ans = None try: ans = date_time.strftime(my_datetime_foramted) except Exception as exc: pass inferred_answer_string = ans #print("my_anwser:", ans) return inferred_answer_string def check_answer_keep_symbol(captcha_text_div_text): is_need_keep_symbol = False # format text keep_symbol_tmp = captcha_text_div_text keep_symbol_tmp = keep_symbol_tmp.replace('也','須') keep_symbol_tmp = keep_symbol_tmp.replace('必須','須') keep_symbol_tmp = keep_symbol_tmp.replace('全都','都') keep_symbol_tmp = keep_symbol_tmp.replace('全部都','都') keep_symbol_tmp = keep_symbol_tmp.replace('一致','相同') keep_symbol_tmp = keep_symbol_tmp.replace('一樣','相同') keep_symbol_tmp = keep_symbol_tmp.replace('相等','相同') if '符號須都相同' in keep_symbol_tmp: is_need_keep_symbol = True if '符號都相同' in keep_symbol_tmp: is_need_keep_symbol = True if '符號須相同' in keep_symbol_tmp: is_need_keep_symbol = True # for: 大小寫含括號需一模一樣 keep_symbol_tmp = keep_symbol_tmp.replace('含', '') keep_symbol_tmp = keep_symbol_tmp.replace('和', '') keep_symbol_tmp = keep_symbol_tmp.replace('與', '') keep_symbol_tmp = keep_symbol_tmp.replace('還有', '') keep_symbol_tmp = keep_symbol_tmp.replace('及', '') keep_symbol_tmp = keep_symbol_tmp.replace('以及', '') keep_symbol_tmp = keep_symbol_tmp.replace('需', '') keep_symbol_tmp = keep_symbol_tmp.replace('必須', '') keep_symbol_tmp = keep_symbol_tmp.replace('而且', '') keep_symbol_tmp = keep_symbol_tmp.replace('且', '') keep_symbol_tmp = keep_symbol_tmp.replace('一模', '') #print("keep_symbol_tmp:", keep_symbol_tmp) if '大小寫括號相同' in keep_symbol_tmp: is_need_keep_symbol = True return is_need_keep_symbol def get_answer_list_from_question_string(registrationsNewApp_div, captcha_text_div_text): show_debug_message = True # debug. show_debug_message = False # online inferred_answer_string = None answer_list = [] CONST_EXAMPLE_SYMBOL = "範例" CONST_INPUT_SYMBOL = "輸入" if captcha_text_div_text is None: captcha_text_div_text = "" # 請在下方空白處輸入引號內文字: # 請回答下列問題,請在下方空格輸入DELIGHT(請以半形輸入法作答,大小寫需要一模一樣) if inferred_answer_string is None: is_use_quota_message = False if "「" in captcha_text_div_text and "」" in captcha_text_div_text: # test for rule#1, it's seem very easy conflict... match_quota_text_items = ["空白","輸入","引號","文字"] is_match_quota_text = True for each_quota_text in match_quota_text_items: if not each_quota_text in captcha_text_div_text: is_match_quota_text = False if is_match_quota_text: is_use_quota_message = True #print("is_use_quota_message:" , is_use_quota_message) if is_use_quota_message: temp_answer = find_between(captcha_text_div_text, "「", "」") temp_answer = temp_answer.strip() if len(temp_answer) > 0: inferred_answer_string = temp_answer #print("find captcha text:" , inferred_answer_string) # 請在下方空白處輸入括號內數字 if inferred_answer_string is None: formated_html_text = captcha_text_div_text.strip() formated_html_text = format_quota_string(formated_html_text) formated_html_text = formated_html_text.replace('請輸入','輸入') formated_html_text = formated_html_text.replace('的','') formated_html_text = formated_html_text.replace('之內','內') formated_html_text = formated_html_text.replace('之中','中') formated_html_text = formated_html_text.replace('括弧','括號') formated_html_text = formated_html_text.replace('引號','括號') formated_html_text = formated_html_text.replace('括號中','括號內') formated_html_text = formated_html_text.replace('數字','文字') is_match_input_quota_text = False if len(formated_html_text) <= 30: if not '\n' in formated_html_text: if '【' in formated_html_text and '】' in formated_html_text: is_match_input_quota_text = True # check target text terms. if is_match_input_quota_text: target_text_list = ["輸入","括號","文字"] for item in target_text_list: if not item in formated_html_text: is_match_input_quota_text = False break if is_match_input_quota_text: temp_answer = find_between(formated_html_text, "【", "】") temp_answer = temp_answer.strip() if len(temp_answer) > 0: temp_answer = temp_answer.replace(' ','') # check raw question. if '數字' in captcha_text_div_text: temp_answer = normalize_chinese_numeric(temp_answer) inferred_answer_string = temp_answer if inferred_answer_string is None: is_use_quota_message = False if "【" in captcha_text_div_text and "】" in captcha_text_div_text: if '下' in captcha_text_div_text and '空' in captcha_text_div_text and CONST_INPUT_SYMBOL in captcha_text_div_text and '引號' in captcha_text_div_text and '字' in captcha_text_div_text: is_use_quota_message = True if '半形' in captcha_text_div_text and CONST_INPUT_SYMBOL in captcha_text_div_text and '引號' in captcha_text_div_text and '字' in captcha_text_div_text: is_use_quota_message = True #print("is_use_quota_message:" , is_use_quota_message) if is_use_quota_message: inferred_answer_string = find_between(captcha_text_div_text, "【", "】") inferred_answer_string = inferred_answer_string.strip() #print("find captcha text:" , inferred_answer_string) # parse '演出日期' if inferred_answer_string is None: inferred_answer_string = get_answer_string_from_web_date(CONST_EXAMPLE_SYMBOL, CONST_INPUT_SYMBOL, registrationsNewApp_div, captcha_text_div_text) # parse '演出時間' if inferred_answer_string is None: inferred_answer_string = get_answer_string_from_web_time(CONST_EXAMPLE_SYMBOL, CONST_INPUT_SYMBOL, registrationsNewApp_div, captcha_text_div_text) # name of event. if inferred_answer_string is None: if "name of event" in captcha_text_div_text: if '(' in captcha_text_div_text and ')' in captcha_text_div_text and 'ans:' in captcha_text_div_text.lower(): target_symbol = "(" star_index = captcha_text_div_text.find(target_symbol) target_symbol = ":" star_index = captcha_text_div_text.find(target_symbol, star_index) target_symbol = ")" end_index = captcha_text_div_text.find(target_symbol, star_index) inferred_answer_string = captcha_text_div_text[star_index+1:end_index] #print("inferred_answer_string:", inferred_answer_string) # 二題式,組合問題。 is_combine_two_question = False if "第一題" in captcha_text_div_text and "第二題" in captcha_text_div_text: is_combine_two_question = True if "Q1." in captcha_text_div_text and "Q2." in captcha_text_div_text: if "二題" in captcha_text_div_text: is_combine_two_question = True if "2題" in captcha_text_div_text: is_combine_two_question = True if "Q1:" in captcha_text_div_text and "Q2:" in captcha_text_div_text: if "二題" in captcha_text_div_text: is_combine_two_question = True if "2題" in captcha_text_div_text: is_combine_two_question = True if "Q1 " in captcha_text_div_text and "Q2 " in captcha_text_div_text: if "二題" in captcha_text_div_text: is_combine_two_question = True if "2題" in captcha_text_div_text: is_combine_two_question = True if is_combine_two_question: inferred_answer_string = None #print("is_combine_two_question:", is_combine_two_question) # still no answer. if inferred_answer_string is None: if not is_combine_two_question: answer_list = get_answer_list_by_question(CONST_EXAMPLE_SYMBOL, CONST_INPUT_SYMBOL, captcha_text_div_text) if show_debug_message: print("guess answer list:", answer_list) else: if show_debug_message: print("skip to guess answer because of combine question...") else: if show_debug_message: print("got an inferred_answer_string:", inferred_answer_string) answer_list = [inferred_answer_string] return answer_list def kktix_reg_captcha_question_text(captcha_inner_div): captcha_text_div = None try: captcha_text_div = captcha_inner_div.find_element(By.TAG_NAME, "p") except Exception as exc: pass print("find p tag(captcha_text_div) fail") print(exc) question_text = None if not captcha_text_div is None: try: question_text = captcha_text_div.text except Exception as exc: pass if question_text is None: question_text = "" return question_text 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 get_kktix_control_label_text(driver): question_text = "" captcha_inner_div = None try: captcha_inner_div = driver.find_element(By.CSS_SELECTOR, 'div > div.code-input > div.control-group > label.control-label') if not captcha_inner_div is None: question_text = remove_html_tags(captcha_inner_div.get_attribute('innerHTML')) except Exception as exc: pass return question_text def set_kktix_control_label_text(driver, config_dict): fail_list = [] answer_list = get_answer_list_from_user_guess_string(config_dict) 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 > 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 get_kktix_question_text(driver): question_text = "" captcha_inner_div = None try: captcha_inner_div = driver.find_element(By.CSS_SELECTOR, 'div.custom-captcha-inner') except Exception as exc: pass if not captcha_inner_div is None: question_text = kktix_reg_captcha_question_text(captcha_inner_div) return question_text def kktix_reg_captcha(driver, config_dict, fail_list, captcha_sound_played, is_finish_checkbox_click, registrationsNewApp_div): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True answer_list = [] is_question_popup = False question_text = get_kktix_question_text(driver) if len(question_text) > 0: is_question_popup = True write_question_to_file(question_text) if len(fail_list)==0: # only play sound once. if not captcha_sound_played: captcha_sound_played = True try: play_sound_while_ordering(config_dict) except Exception as exc: pass answer_list = get_answer_list_from_user_guess_string(config_dict) if len(answer_list)==0: if config_dict["advanced"]["auto_guess_options"]: answer_list = 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 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. input_text_css = 'div.custom-captcha-inner > div > div > input' 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) return fail_list, captcha_sound_played, is_question_popup def kktix_reg_new_main(driver, config_dict, fail_list, captcha_sound_played, is_finish_checkbox_click): 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_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: fail_list, captcha_sound_played, is_question_popup = kktix_reg_captcha(driver, config_dict, fail_list, captcha_sound_played, is_finish_checkbox_click, registrationsNewApp_div) if not is_question_popup: # no captcha text popup, goto next page. control_text = get_kktix_control_label_text(driver) if show_debug_message: print("control_text:", control_text) if len(control_text) == 0: click_ret = kktix_press_next_button(driver) else: #set_kktix_control_label_text(driver, config_dict) # input by maxbox plus extension. pass else: if is_need_refresh: 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, captcha_sound_played def kktix_get_registerStatus(driver, event_code): html_result = None url = "https://kktix.com/g/events/%s/register_info" % (event_code) #print('event_code:',event_code) #print("url:", url) headers = {"Accept-Language": "zh-TW,zh;q=0.5", 'User-Agent': USER_AGENT} try: html_result = requests.get(url , headers=headers, timeout=0.7, allow_redirects=False) except Exception as exc: html_result = None print("send reg_info request fail:") print(exc) registerStatus = None if not html_result is None: status_code = html_result.status_code #print("status_code:",status_code) if status_code == 200: html_text = html_result.text #print("html_text:", html_text) try: jsLoads = json.loads(html_text) if 'inventory' in jsLoads: if 'registerStatus' in jsLoads['inventory']: registerStatus = jsLoads['inventory']['registerStatus'] except Exception as exc: print("load reg_info json fail:") print(exc) pass #print("registerStatus:", registerStatus) return registerStatus def kktix_check_register_status(driver, url): #ex: https://xxx.kktix.cc/events/xxx prefix_list = ['.com/events/','.cc/events/'] postfix = '/registrations/new' is_match_event_code = False event_code = "" for prefix in prefix_list: event_code = find_between(url,prefix,postfix) if len(event_code) > 0: is_match_event_code = True #print('event_code:',event_code) break if is_match_event_code: 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 # use javascritp version only. is_match_event_code = False registerStatus = None if is_match_event_code: kktix_get_registerStatus(driver, 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) is_finish_checkbox_click = False is_dom_ready, is_finish_checkbox_click = kktix_check_agree_checkbox(driver, config_dict) return is_dom_ready, is_finish_checkbox_click # 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 = 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 = 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 = format_keyword_string(td_area.text) #row_text = row.text row_html = row.get_attribute('innerHTML') row_text = 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 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 = 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 = get_answer_list_from_user_guess_string(config_dict) if len(answer_list)==0: if config_dict["advanced"]["auto_guess_options"]: answer_list = 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 = 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 = 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 = 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 = 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 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 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 '