#!/usr/bin/env python3 #encoding=utf-8 #執行方式:python chrome_tixcraft.py 或 python3 chrome_tixcraft.py import json import logging import os import pathlib import platform import random import re import sys import time #import jieba from datetime import datetime 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 DrissionPage import ChromiumPage logging.basicConfig() logger = logging.getLogger('logger') import warnings # for check kktix reg_info import requests from urllib3.exceptions import InsecureRequestWarning warnings.simplefilter('ignore',InsecureRequestWarning) import ssl ssl._create_default_https_context = ssl._create_unverified_context # ocr import base64 try: import ddddocr from NonBrowser import NonBrowser except Exception as exc: pass import argparse import webbrowser import chromedriver_autoinstaller CONST_APP_VERSION = "MaxBot (2023.12.17)" CONST_MAXBOT_CONFIG_FILE = "settings.json" CONST_MAXBOT_LAST_URL_FILE = "MAXBOT_LAST_URL.txt" CONST_MAXBOT_INT28_FILE = "MAXBOT_INT28_IDLE.txt" CONST_MAXBOT_ANSWER_ONLINE_FILE = "MAXBOT_ONLINE_ANSWER.txt" CONST_MAXBOT_QUESTION_FILE = "MAXBOT_QUESTION.txt" CONST_HOMEPAGE_DEFAULT = "https://tixcraft.com" URL_CHROME_DRIVER = 'https://chromedriver.chromium.org/' CONST_CHROME_VERSION_NOT_MATCH_EN="Please download the WebDriver version to match your browser version." CONST_CHROME_VERSION_NOT_MATCH_TW="請下載與您瀏覽器相同版本的WebDriver版本,或更新您的瀏覽器版本。" CONST_KKTIX_SIGN_IN_URL = "https://kktix.com/users/sign_in?back_to=%s" CONST_FAMI_SIGN_IN_URL = "https://www.famiticket.com.tw/Home/User/SignIn" CONST_CITYLINE_SIGN_IN_URL = "https://www.cityline.com/Login.html?targetUrl=https%3A%2F%2Fwww.cityline.com%2FEvents.html" CONST_URBTIX_SIGN_IN_URL = "https://www.urbtix.hk/member-login" CONST_KHAM_SIGN_IN_URL = "https://kham.com.tw/application/UTK13/UTK1306_.aspx" CONST_TICKET_SIGN_IN_URL = "https://ticket.com.tw/application/utk13/utk1306_.aspx" CONST_HKTICKETING_SIGN_IN_URL = "https://premier.hkticketing.com/Secure/ShowLogin.aspx" 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_AUTO_RELOAD_RANDOM_DELAY_MAX_SECOND = 4 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" 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 = [] if config_dict["advanced"]["adblock_plus_enable"]: extension_list.append(os.path.join(webdriver_path,"Adblock_3.21.1.0.crx")) extension_list.append(os.path.join(webdriver_path,"Maxbot_1.0.0.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): browser=config_dict["browser"] chrome_options = webdriver.ChromeOptions() if browser=="edge": chrome_options = webdriver.EdgeOptions() if 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: chrome_options.set_capability("goog:loggingPrefs",{"performance": "ALL"}) # PS: this is crx version. 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-features=TranslateUI') chrome_options.add_argument('--disable-translate') chrome_options.add_argument('--lang=zh-TW') chrome_options.add_argument('--disable-web-security') chrome_options.add_argument("--no-sandbox"); chrome_options.add_argument("--disable-popup-blocking") chrome_options.add_argument("--disable-notifications") # 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 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.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(URL_CHROME_DRIVER) 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.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 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 = 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 "Maxbot_" in ext: target_path = ext target_path = os.path.join(target_path, "data") target_path = os.path.join(target_path, "settings.json") #print("save as to:", target_path) if os.path.exists(target_path): with open(target_path, 'w') as outfile: json.dump(config_dict, outfile) load_extension_path += ("," + os.path.abspath(ext)) 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-features=TranslateUI') options.add_argument('--disable-translate') options.add_argument('--lang=zh-TW') options.add_argument('--disable-web-security') options.add_argument("--no-sandbox"); options.add_argument("--disable-popup-blocking") options.add_argument("--disable-notifications") 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.install(path=webdriver_path, make_version_dir=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.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 None..., try again..') 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): global driver # 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 = "" if len(homepage) == 0: homepage = CONST_HOMEPAGE_DEFAULT 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/*' ,'*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["tixcraft"]["date_auto_select"]["mode"] date_keyword = config_dict["tixcraft"]["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_random_delay"]: time.sleep(random.randint(0,CONST_AUTO_RELOAD_RANDOM_DELAY_MAX_SECOND)) 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["tixcraft"]["date_auto_select"]["mode"] date_keyword = config_dict["tixcraft"]["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 = False if len(keyword_string) > 0: area_keyword_exclude_array = [] try: area_keyword_exclude_array = json.loads("["+ keyword_string +"]") except Exception as exc: area_keyword_exclude_array = [] for exclude_item_list in area_keyword_exclude_array: if len(row_text) > 0: if ' ' in exclude_item_list: area_keyword_array = exclude_item_list.split(' ') is_match_all_exclude = True for exclude_item in area_keyword_array: exclude_item = format_keyword_string(exclude_item) if not exclude_item in row_text: is_match_all_exclude = False if is_match_all_exclude: row_text = "" is_match_keyword = True break else: exclude_item = format_keyword_string(exclude_item_list) if exclude_item in row_text: row_text = "" is_match_keyword = True 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_random_delay"]: time.sleep(random.randint(0,CONST_AUTO_RELOAD_RANDOM_DELAY_MAX_SECOND)) 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_random_delay"]: time.sleep(random.randint(0,CONST_AUTO_RELOAD_RANDOM_DELAY_MAX_SECOND)) 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 tixcraft_ticket_number_auto_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) is_button_clicked = False 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) 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)) except Exception as exc: if show_debug_message: print(exc) pass 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 len(config_dict["advanced"]["online_dictionary_url"]) > 0: 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 def tixcraft_ticket_main(driver, config_dict, ocr, Captcha_Browser, domain_name): # use extension instead of selenium. #tixcraft_ticket_main_agree(driver, config_dict) is_ticket_number_assigned = tixcraft_assign_ticket_number(driver, config_dict) if not is_ticket_number_assigned: # should not enter this block, due to extension done. ticket_number = str(config_dict["ticket_number"]) is_ticket_number_assigned = tixcraft_ticket_number_auto_fill(driver, select_obj, ticket_number) # must wait ticket number assign to focus captcha. if is_ticket_number_assigned: tixcraft_ticket_main_ocr(driver, config_dict, ocr, Captcha_Browser, domain_name) def tixcraft_ticket_main_ocr(driver, config_dict, ocr, Captcha_Browser, domain_name): away_from_keyboard_enable = config_dict["ocr_captcha"]["force_submit"] if not config_dict["ocr_captcha"]["enable"]: away_from_keyboard_enable = False ocr_captcha_image_source = config_dict["ocr_captcha"]["image_source"] if not config_dict["ocr_captcha"]["enable"]: tixcraft_keyin_captcha_code(driver) else: previous_answer = None last_url, is_quit_bot = get_current_url(driver) for redo_ocr in range(99): is_need_redo_ocr, previous_answer, is_form_sumbited = tixcraft_auto_ocr(driver, ocr, away_from_keyboard_enable, previous_answer, Captcha_Browser, ocr_captcha_image_source, domain_name) if is_form_sumbited: # start next loop. break if not away_from_keyboard_enable: break if not is_need_redo_ocr: break current_url, is_quit_bot = get_current_url(driver) if current_url != last_url: break def kktix_confirm_order_button(driver): ret = False wait = WebDriverWait(driver, 1) next_step_button = None try: # method #3 wait next_step_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, 'div.form-actions a.btn-primary'))) if not next_step_button is None: if next_step_button.is_enabled(): next_step_button.click() ret = True except Exception as exc: print("wait form-actions div wait to be clickable Exception:") #print(exc) pass 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.execute_script("arguments[0].click();", next_step_button) ret = True except Exception as exc: pass return ret # PS: There are two "Next" button in kktix. # : 1: /events/xxx # : 2: /events/xxx/registrations/new # : This is ONLY for case-1, because case-2 lenght >5 def kktix_events_press_next_button(driver): is_button_clicked = force_press_button(driver, By.CSS_SELECTOR,'.tickets > a.btn-point') return is_button_clicked # : This is for case-2 next button. def kktix_press_next_button(driver): ret = False wait = WebDriverWait(driver, 1) next_step_button = None try: # method #1 #form_actions_div = None #form_actions_div = driver.find_element(By.CSS_SELECTOR, '#registrationsNewApp') #next_step_button = form_actions_div.find_element(By.CSS_SELECTOR, 'div.form-actions button.btn-primary') # method #2 # next_step_button = driver.find_element(By.CSS_SELECTOR, '#registrationsNewApp div.form-actions button.btn-primary') # method #3 wait next_step_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#registrationsNewApp div.form-actions button.btn-primary'))) if not next_step_button is None: if next_step_button.is_enabled(): next_step_button.click() ret = True except Exception as exc: print("wait form-actions div wait to be clickable Exception:") print(exc) #pass 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.execute_script("arguments[0].click();", next_step_button) ret = True except Exception as exc: pass return ret def kktix_captcha_inputed_text(captcha_inner_div): ret = "" if not captcha_inner_div is None: try: captcha_password_text = captcha_inner_div.find_element(By.TAG_NAME, "input") if not captcha_password_text is None: ret = captcha_password_text.get_attribute('value') else: print("find captcha input field fail") except Exception as exc: print("find captcha_inner_div Exception:") #print(exc) pass return ret def kktix_input_captcha_text(captcha_password_input_element, inferred_answer_string, force_overwrite = False): show_debug_message = True # debug. show_debug_message = False # online is_captcha_sent = False inputed_captcha_text = "" if not captcha_password_input_element is None: if force_overwrite: try: captcha_password_input_element.send_keys(inferred_answer_string) print("send captcha keys:" + inferred_answer_string) is_captcha_sent = True except Exception as exc: pass else: # not force overwrite: inputed_captcha_text = None try: inputed_captcha_text = captcha_password_input_element.get_attribute('value') except Exception as exc: pass if inputed_captcha_text is None: inputed_captcha_text = "" if len(inputed_captcha_text) == 0: try: captcha_password_input_element.send_keys(inferred_answer_string) print("send captcha keys:" + inferred_answer_string) is_captcha_sent = True except Exception as exc: pass else: if inputed_captcha_text == inferred_answer_string: is_captcha_sent = True return is_captcha_sent def kktix_travel_price_list(driver, config_dict, kktix_area_auto_select_mode, kktix_area_keyword): show_debug_message = True # debug. show_debug_message = False # online if config_dict["advanced"]["verbose"]: show_debug_message = True ticket_number = config_dict["ticket_number"] areas = None is_ticket_number_assigned = False ticket_price_list = None try: ticket_price_list = driver.find_elements(By.CSS_SELECTOR, 'div.display-table-row') except Exception as exc: ticket_price_list = None print("find ticket-price span Exception:") print(exc) pass price_list_count = 0 if not ticket_price_list is None: price_list_count = len(ticket_price_list) if show_debug_message: print("found price count:", price_list_count) else: print("find ticket-price span fail") is_travel_interrupted = False if price_list_count > 0: areas = [] kktix_area_keyword_array = kktix_area_keyword.split(' ') kktix_area_keyword_1 = kktix_area_keyword_array[0] kktix_area_keyword_1_and = "" if len(kktix_area_keyword_array) > 1: kktix_area_keyword_1_and = kktix_area_keyword_array[1] # clean stop word. kktix_area_keyword_1 = format_keyword_string(kktix_area_keyword_1) kktix_area_keyword_1_and = format_keyword_string(kktix_area_keyword_1_and) if show_debug_message: print('kktix_area_keyword_1:', kktix_area_keyword_1) print('kktix_area_keyword_1_and:', kktix_area_keyword_1_and) for row in ticket_price_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: is_travel_interrupted = True if show_debug_message: print(exc) # error, exit loop break if len(row_text) > 0: if '未開賣' in row_text: row_text = "" if '暫無票' in row_text: row_text = "" if '已售完' in row_text: row_text = "" if 'Sold Out' in row_text: row_text = "" if '完売' in row_text: row_text = "" if not(' 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 is_travel_interrupted: # not sure to break or continue..., maybe break better. break else: if show_debug_message: print("no any price list found.") pass # unknow issue... if is_travel_interrupted: pass return 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_ticket_number_assigned, matched_blocks = kktix_travel_price_list(driver, config_dict, auto_select_mode, kktix_area_keyword) target_area = None if not is_ticket_number_assigned: target_area = get_target_item_from_matched_list(matched_blocks, auto_select_mode) is_need_refresh = False 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_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_need_refresh = False is_finish_checkbox_click = False agree_checkbox = None try: 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 if not agree_checkbox is None: is_finish_checkbox_click = force_check_checkbox(driver, agree_checkbox) else: is_need_refresh = True if is_need_refresh: print("find person_agree_terms checkbox fail, do refresh page.") return is_need_refresh, 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 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;} 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 html_result = None if is_match_event_code: url = "https://kktix.com/g/events/%s/register_info" % (event_code) #print('event_code:',event_code) #print("url:", url) user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36' 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 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.ticket-unit > 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 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_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_ticket_number_assigned, is_need_refresh_tmp = kktix_assign_ticket_number(driver, config_dict, area_keyword_item) # 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_ticket_number_assigned, is_need_refresh = kktix_assign_ticket_number(driver, config_dict, "") # 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: 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_random_delay"]: time.sleep(random.randint(0,CONST_AUTO_RELOAD_RANDOM_DELAY_MAX_SECOND)) return fail_list, captcha_sound_played def kktix_reg_auto_reload(driver, url, config_dict, kktix_register_status_last): registerStatus = kktix_register_status_last # auto refresh for area list page. is_need_refresh = False if not is_need_refresh: if registerStatus is None: # current version, change refresh event from selenium to javascript. registerStatus = kktix_check_register_status(driver, url) # for request solution, refresh on selenium. if not registerStatus is None: print("registerStatus:", registerStatus) # OUT_OF_STOCK if registerStatus != 'IN_STOCK': is_need_refresh = True is_finish_checkbox_click = False if not is_need_refresh: is_need_refresh, is_finish_checkbox_click = kktix_check_agree_checkbox(driver, config_dict) if not is_finish_checkbox_click: # retry again. is_need_refresh, is_finish_checkbox_click = kktix_check_agree_checkbox(driver, config_dict) if is_need_refresh: try: print("try to refresh page...") driver.refresh() except Exception as exc: #print("refresh fail") pass if config_dict["advanced"]["auto_reload_random_delay"]: time.sleep(random.randint(0,CONST_AUTO_RELOAD_RANDOM_DELAY_MAX_SECOND)) return is_need_refresh, 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["tixcraft"]["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["tixcraft"]["date_auto_select"]["mode"] date_keyword = config_dict["tixcraft"]["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 "