From 60785369ea60359fe15d97b1b449d1817ef69497 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 3 Apr 2024 19:51:36 +0800 Subject: [PATCH] 2024-03-21, add new webdirver: nodriver --- chrome_tixcraft.py | 456 +--------------- config_launcher.py | 2 +- kktix_signout.py | 46 +- kktix_status.py | 2 +- nodriver_tixcraft.py | 1188 ++++++++++++++++++++++++++++++++++++++++++ settings.py | 19 +- util.py | 427 +++++++++++++++ 7 files changed, 1671 insertions(+), 469 deletions(-) create mode 100644 nodriver_tixcraft.py diff --git a/chrome_tixcraft.py b/chrome_tixcraft.py index f4a3e98..7b7bedd 100644 --- a/chrome_tixcraft.py +++ b/chrome_tixcraft.py @@ -44,7 +44,7 @@ except Exception as exc: print(exc) pass -CONST_APP_VERSION = "MaxBot (2024.03.20)" +CONST_APP_VERSION = "MaxBot (2024.03.21)" CONST_MAXBOT_ANSWER_ONLINE_FILE = "MAXBOT_ONLINE_ANSWER.txt" CONST_MAXBOT_CONFIG_FILE = "settings.json" @@ -2719,62 +2719,7 @@ def kktix_assign_ticket_number(driver, config_dict, kktix_area_keyword): return is_dom_ready, is_ticket_number_assigned, is_need_refresh -def kktix_get_web_datetime(registrationsNewApp_div): - show_debug_message = True # debug. - show_debug_message = False # online - web_datetime = None - - is_found_web_datetime = False - - el_web_datetime_list = None - if not registrationsNewApp_div is None: - try: - el_web_datetime_list = registrationsNewApp_div.find_elements(By.TAG_NAME, 'td') - except Exception as exc: - if show_debug_message: - print("find td.ng-binding Exception") - print(exc) - pass - #print("is_found_web_datetime", is_found_web_datetime) - - if not el_web_datetime_list is None: - el_web_datetime_list_count = len(el_web_datetime_list) - if el_web_datetime_list_count > 0: - el_web_datetime = None - for el_web_datetime in el_web_datetime_list: - el_web_datetime_text = None - try: - el_web_datetime_text = el_web_datetime.text - if show_debug_message: - print("el_web_datetime_text:", el_web_datetime_text) - except Exception as exc: - if show_debug_message: - print('parse web datetime fail:') - print(exc) - pass - - if not el_web_datetime_text is None: - if len(el_web_datetime_text) > 0: - now = datetime.now() - #print("now:", now) - for guess_year in range(now.year,now.year+3): - current_year = str(guess_year) - if current_year in el_web_datetime_text: - if '/' in el_web_datetime_text: - web_datetime = el_web_datetime_text - is_found_web_datetime = True - break - if is_found_web_datetime: - break - else: - print("find td.ng-binding fail") - - if show_debug_message: - print('is_found_web_datetime:', is_found_web_datetime) - print('web_datetime:', web_datetime) - - return web_datetime def kktix_check_agree_checkbox(driver, config_dict): show_debug_message = True # debug. @@ -2851,375 +2796,9 @@ def force_check_checkbox(driver, agree_checkbox): is_finish_checkbox_click = True return is_finish_checkbox_click -def get_answer_string_from_web_date(CONST_EXAMPLE_SYMBOL, CONST_INPUT_SYMBOL, registrationsNewApp_div, captcha_text_div_text): - show_debug_message = True # debug. - show_debug_message = False # online - - inferred_answer_string = None - - is_need_parse_web_datetime = False - # '半形阿拉伯數字' & '半形數字' - if '半形' in captcha_text_div_text and '字' in captcha_text_div_text: - if '演出日期' in captcha_text_div_text: - is_need_parse_web_datetime = True - if '活動日期' in captcha_text_div_text: - is_need_parse_web_datetime = True - if '表演日期' in captcha_text_div_text: - is_need_parse_web_datetime = True - if '開始日期' in captcha_text_div_text: - is_need_parse_web_datetime = True - if '演唱會日期' in captcha_text_div_text: - is_need_parse_web_datetime = True - if '展覽日期' in captcha_text_div_text: - is_need_parse_web_datetime = True - if '音樂會日期' in captcha_text_div_text: - is_need_parse_web_datetime = True - if 'the date of the show you purchased' in captcha_text_div_text: - is_need_parse_web_datetime = True - - if show_debug_message: - print("is_need_parse_web_datetime:", is_need_parse_web_datetime) - - if is_need_parse_web_datetime: - web_datetime = kktix_get_web_datetime(registrationsNewApp_div) - if not web_datetime is None: - if show_debug_message: - print("web_datetime:", web_datetime) - - captcha_text_formatted = util.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 = util.find_continuous_number(right_part) - - my_anwser_formated = util.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 = util.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 = util.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 = util.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 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 = util.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 = util.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 = util.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 = util.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 = util.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 = util.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 @@ -3316,7 +2895,7 @@ def get_kktix_question_text(driver): question_text = kktix_reg_captcha_question_text(captcha_inner_div) return question_text -def kktix_reg_captcha(driver, config_dict, fail_list, is_finish_checkbox_click, registrationsNewApp_div): +def kktix_reg_captcha(driver, config_dict, fail_list, registrationsNewApp_div): show_debug_message = True # debug. show_debug_message = False # online @@ -3334,7 +2913,7 @@ def kktix_reg_captcha(driver, config_dict, fail_list, is_finish_checkbox_click, answer_list = util.get_answer_list_from_user_guess_string(config_dict, CONST_MAXBOT_ANSWER_ONLINE_FILE) if len(answer_list)==0: if config_dict["advanced"]["auto_guess_options"]: - answer_list = get_answer_list_from_question_string(registrationsNewApp_div, question_text) + answer_list = util.get_answer_list_from_question_string(registrationsNewApp_div, question_text) inferred_answer_string = "" for answer_item in answer_list: @@ -3359,7 +2938,7 @@ def kktix_reg_captcha(driver, config_dict, fail_list, is_finish_checkbox_click, return fail_list, is_question_popup -def kktix_reg_new_main(driver, config_dict, fail_list, played_sound_ticket, is_finish_checkbox_click): +def kktix_reg_new_main(driver, config_dict, fail_list, played_sound_ticket): show_debug_message = True # debug. show_debug_message = False # online @@ -3394,6 +2973,7 @@ def kktix_reg_new_main(driver, config_dict, fail_list, played_sound_ticket, is_f is_need_refresh_final = True for area_keyword_item in area_keyword_array: + is_need_refresh_tmp = False is_dom_ready, is_ticket_number_assigned, is_need_refresh_tmp = kktix_assign_ticket_number(driver, config_dict, area_keyword_item) if not is_dom_ready: @@ -3425,7 +3005,7 @@ def kktix_reg_new_main(driver, config_dict, fail_list, played_sound_ticket, is_f played_sound_ticket = True # whole event question. - fail_list, is_question_popup = kktix_reg_captcha(driver, config_dict, fail_list, is_finish_checkbox_click, registrationsNewApp_div) + fail_list, is_question_popup = kktix_reg_captcha(driver, config_dict, fail_list, registrationsNewApp_div) # single option question if not is_question_popup: @@ -3526,11 +3106,6 @@ def kktix_reg_auto_reload(driver, url, config_dict): if is_reload_at_webdriver: kktix_check_register_status(driver, url) - is_finish_checkbox_click = False - is_dom_ready, is_finish_checkbox_click = kktix_check_agree_checkbox(driver, config_dict) - - return is_dom_ready, is_finish_checkbox_click - # PURPOSE: get target area list. # PS: this is main block, use keyword to get rows. @@ -6546,9 +6121,11 @@ def kktix_main(driver, url, config_dict, kktix_dict): if '/registrations/new' in url: kktix_dict["start_time"] = time.time() + kktix_reg_auto_reload(driver, url, config_dict) + is_dom_ready = False is_finish_checkbox_click = False - is_dom_ready, is_finish_checkbox_click = kktix_reg_auto_reload(driver, url, config_dict) + is_dom_ready, is_finish_checkbox_click = kktix_check_agree_checkbox(driver, config_dict) if not is_dom_ready: # reset answer fail list. @@ -6557,7 +6134,7 @@ def kktix_main(driver, url, config_dict, kktix_dict): else: # check is able to buy. if config_dict["kktix"]["auto_fill_ticket_number"]: - kktix_dict["fail_list"], kktix_dict["played_sound_ticket"] = kktix_reg_new_main(driver, config_dict, kktix_dict["fail_list"], kktix_dict["played_sound_ticket"], is_finish_checkbox_click) + kktix_dict["fail_list"], kktix_dict["played_sound_ticket"] = kktix_reg_new_main(driver, config_dict, kktix_dict["fail_list"], kktix_dict["played_sound_ticket"]) kktix_dict["done_time"] = time.time() else: is_event_page = False @@ -7289,7 +6866,7 @@ def ibon_verification_question(driver, fail_list, config_dict): answer_list = util.get_answer_list_from_user_guess_string(config_dict, CONST_MAXBOT_ANSWER_ONLINE_FILE) if len(answer_list)==0: if config_dict["advanced"]["auto_guess_options"]: - answer_list = get_answer_list_from_question_string(None, question_text) + answer_list = util.get_answer_list_from_question_string(None, question_text) inferred_answer_string = "" if len(answer_list) > 0: @@ -11230,7 +10807,7 @@ def resize_window(driver, config_dict): target_array = config_dict["advanced"]["window_size"].split(",") driver.set_window_size(int(target_array[0]), int(target_array[1])) -def launch_maxbot(filename, homepage="", kktix_account = "", kktix_password="", headless=""): +def launch_maxbot(filename, homepage="", kktix_account = "", kktix_password="", headless="", script_name="chrome_tixcraft"): cmd_argument = [] if len(filename) > 0: cmd_argument.append('--input=' + filename) @@ -11247,14 +10824,14 @@ def launch_maxbot(filename, homepage="", kktix_account = "", kktix_password="", if hasattr(sys, 'frozen'): print("execute in frozen mode") # check platform here. - cmd = './chrome_tixcraft' + ' '.join(cmd_argument) + cmd = './' + script_name + ' '.join(cmd_argument) if platform.system() == 'Darwin': print("execute MacOS python script") if platform.system() == 'Linux': print("execute linux binary") if platform.system() == 'Windows': print("execute .exe binary.") - cmd = 'chrome_tixcraft.exe ' + ' '.join(cmd_argument) + cmd = script_name + '.exe ' + ' '.join(cmd_argument) subprocess.Popen(cmd, shell=True, cwd=working_dir) else: interpreter_binary = 'python' @@ -11263,8 +10840,7 @@ def launch_maxbot(filename, homepage="", kktix_account = "", kktix_password="", interpreter_binary = 'python3' interpreter_binary_alt = 'python' print("execute in shell mode.") - - script_name = "chrome_tixcraft" + try: print('try', interpreter_binary) cmd_array = [interpreter_binary, script_name + '.py'] + cmd_argument @@ -11535,7 +11111,7 @@ def test_captcha_model(): #captcha_text_div_text = "2. 以下那齣並不是OffGun有份演出的劇集?(請以半形數字及細楷英文字母於下方輸入答案)\n2m:《我的貓貓男友》\n4v:《愛情理論》\n6k:《Not Me》" #captcha_text_div_text = "夏賢尚的官方粉絲名稱為? What is the name of Ha Hyun Sang's official fandom? 1. PET / 2. PAN / 3. PENCIL / 4. PEN (請填寫選項「純數字」/ Please only enter the number)" #captcha_text_div_text = "夏賢尚的官方粉絲名稱為? What is the name of Ha Hyun Sang's official fandom? A. PET / B. PAN / C. PENCIL / D. PEN (請填寫選項「純數字」/ Please only enter the number)" - answer_list = get_answer_list_from_question_string(None, captcha_text_div_text) + answer_list = util.get_answer_list_from_question_string(None, captcha_text_div_text) print("answer_list:", answer_list) ocr = ddddocr.DdddOcr(show_ad=False, beta=True) diff --git a/config_launcher.py b/config_launcher.py index f99e2b1..fea3812 100644 --- a/config_launcher.py +++ b/config_launcher.py @@ -25,7 +25,7 @@ import webbrowser import util -CONST_APP_VERSION = "MaxBot (2024.03.20)" +CONST_APP_VERSION = "MaxBot (2024.03.21)" CONST_MAXBOT_LAUNCHER_FILE = "config_launcher.json" CONST_MAXBOT_CONFIG_FILE = "settings.json" diff --git a/kktix_signout.py b/kktix_signout.py index 42ef894..5550687 100644 --- a/kktix_signout.py +++ b/kktix_signout.py @@ -13,10 +13,12 @@ import requests import util -CONST_APP_VERSION = "MaxBot (2024.03.20)" +CONST_APP_VERSION = "MaxBot (2024.03.21)" CONST_MAXBOT_CONFIG_FILE = "settings.json" USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" +PROFILE_URL = "https://kktix.com/users/edit" +SIGNIN_URL = "https://kktix.com/users/sign_in" def load_json(): app_root = util.get_app_root() @@ -31,9 +33,6 @@ def load_json(): return config_filepath, config_dict def kktix_signin_requests(kktix_account, kktix_password): - profile_url = "https://kktix.com/users/edit" - signin_url = "https://kktix.com/users/sign_in" - import urllib.parse headers = { "accept-language": "zh-TW;q=0.7", @@ -89,23 +88,17 @@ def kktix_signin_requests(kktix_account, kktix_password): except Exception as exc: print(exc) -async def kktix_signin_nodriver(kktix_account, kktix_password): - driver = await uc.start() - profile_url = "https://kktix.com/users/edit" - signin_url = "https://kktix.com/users/sign_in" - #signout_url = "https://kktix.com/users/sign_out" - +async def kktix_signin_nodriver(tab, kktix_account, kktix_password): while True: try: - tab = await driver.get(signin_url) + #html = await tab.get_content() #await tab.sleep(0.1) #print(html) - x = await tab.js_dumps('window') - #print(x) - #print(x["location"]["href"]) - if x["location"]["href"]=="signin_url": + x_window = await tab.js_dumps('window') + #print(x_window["location"]["href"]) + if x_window["location"]["href"]==SIGNIN_URL: account = await tab.select("#user_login") await account.send_keys(kktix_account) #await tab.sleep(0.1) @@ -114,15 +107,17 @@ async def kktix_signin_nodriver(kktix_account, kktix_password): await password.send_keys(kktix_password) #await tab.sleep(0.1) - submit = await tab.select("input[type='submit'][name]") + submit = await tab.select("div.form-actions a.btn-primary") await submit.click() await tab.sleep(0.5) - #tab = await tab.get(signout_url) + #tab = await tab.get(SIGNOUT_URL) signout = await tab.select("a[href='/users/sign_out']") await signout.click() await tab.sleep(0.5) + + tab = await tab.get(SIGNIN_URL) except Exception as e: print(e) @@ -131,9 +126,18 @@ async def kktix_signin_nodriver(kktix_account, kktix_password): pass +#def kktix_signout_main(config_dict): +async def kktix_signout_main(config_dict): + conf = util.get_extension_config() + driver = await uc.start(conf) + tab = await driver.get(SIGNIN_URL) + + #if not config_dict["advanced"]["headless"]: + if len(config_dict["advanced"]["window_size"]) > 0: + if "," in config_dict["advanced"]["window_size"]: + target_array = config_dict["advanced"]["window_size"].split(",") + await tab.set_window_size(left=20, top=20, width=int(target_array[0]), height=int(target_array[1])) -#def kktix_account_loop(config_dict): -async def kktix_account_loop(config_dict): kktix_account = config_dict["advanced"]["kktix_account"] kktix_password = config_dict["advanced"]["kktix_password_plaintext"].strip() if kktix_password == "": @@ -143,7 +147,7 @@ async def kktix_account_loop(config_dict): #print("kktix_password:", kktix_password) #kktix_signin_requests(kktix_account, kktix_password) - await kktix_signin_nodriver(kktix_account, kktix_password) + await kktix_signin_nodriver(tab, kktix_account, kktix_password) #def main(args): @@ -159,7 +163,7 @@ async def main(args): if len(config_dict["advanced"]["kktix_account"]) > 0: #kktix_account_loop(config_dict) - await kktix_account_loop(config_dict) + await kktix_signout_main(config_dict) else: print("請輸入 kktix_account") diff --git a/kktix_status.py b/kktix_status.py index 0d31e4b..27adf10 100644 --- a/kktix_status.py +++ b/kktix_status.py @@ -29,7 +29,7 @@ from datetime import datetime import util -CONST_APP_VERSION = "MaxBot (2024.03.20)" +CONST_APP_VERSION = "MaxBot (2024.03.21)" CONST_MAXBOT_CONFIG_FILE = "settings.json" CONST_MAXBOT_KKTIX_CONFIG_FILE = "kktix.json" diff --git a/nodriver_tixcraft.py b/nodriver_tixcraft.py new file mode 100644 index 0000000..9c10a4f --- /dev/null +++ b/nodriver_tixcraft.py @@ -0,0 +1,1188 @@ +#!/usr/bin/env python3 +#encoding=utf-8 +#執行方式:python chrome_tixcraft.py 或 python3 chrome_tixcraft.py +import argparse +import base64 +import json +import logging +import os +import pathlib +import platform +import random +import ssl +import subprocess +import sys +import threading +import time +import warnings +import webbrowser +from datetime import datetime + +import nodriver as uc +from nodriver import cdp +from nodriver.core.config import Config +from urllib3.exceptions import InsecureRequestWarning + +import util +from NonBrowser import NonBrowser + +try: + import ddddocr +except Exception as exc: + print(exc) + pass + +CONST_APP_VERSION = "MaxBot (2024.03.21)" + +CONST_MAXBOT_ANSWER_ONLINE_FILE = "MAXBOT_ONLINE_ANSWER.txt" +CONST_MAXBOT_CONFIG_FILE = "settings.json" +CONST_MAXBOT_EXTENSION_NAME = "Maxbotplus_1.0.0" +CONST_MAXBOT_INT28_FILE = "MAXBOT_INT28_IDLE.txt" +CONST_MAXBOT_LAST_URL_FILE = "MAXBOT_LAST_URL.txt" +CONST_MAXBOT_QUESTION_FILE = "MAXBOT_QUESTION.txt" +CONST_MAXBLOCK_EXTENSION_NAME = "Maxblockplus_1.0.0" +CONST_MAXBLOCK_EXTENSION_FILTER =[ +"*google-analytics.com/*", +"*googletagmanager.com/*", +"*googletagservices.com/*", +"*lndata.com/*", +"*a.amnet.tw/*", +"*adx.c.appier.net/*", +"*clarity.ms/*", +"*cloudfront.com/*", +"*cms.analytics.yahoo.com/*", +"*doubleclick.net/*", +"*e2elog.fetnet.net/*", +"*fundingchoicesmessages.google.com/*", +"*ghtinc.com/*", +"*match.adsrvr.org/*", +"*onead.onevision.com.tw/*", +"*popin.cc/*", +"*rollbar.com/*", +"*sb.scorecardresearch.com/*", +"*tagtoo.co/*", +"*.ssp.hinet.net/*", +"*ticketmaster.sg/js/adblock*", +"*.googlesyndication.com/*", +"*treasuredata.com/*", +"*play.google.com/log?*", +"*www.youtube.com/youtubei/v1/player/heartbeat*", +"*tixcraft.com/js/analytics.js*", +"*ticketmaster.sg/js/adblock.js*", +"*img.uniicreative.com/*", +"*cdn.cookielaw.org/*", +"*tixcraft.com/js/custom.js*", +"*tixcraft.com/js/common.js*", +"*cdnjs.cloudflare.com/ajax/libs/clipboard.js/*"] + +CONST_CITYLINE_SIGN_IN_URL = "https://www.cityline.com/Login.html?targetUrl=https%3A%2F%2Fwww.cityline.com%2FEvents.html" +CONST_FAMI_SIGN_IN_URL = "https://www.famiticket.com.tw/Home/User/SignIn" +CONST_HKTICKETING_SIGN_IN_URL = "https://premier.hkticketing.com/Secure/ShowLogin.aspx" +CONST_KHAM_SIGN_IN_URL = "https://kham.com.tw/application/UTK13/UTK1306_.aspx" +CONST_KKTIX_SIGN_IN_URL = "https://kktix.com/users/sign_in?back_to=%s" +CONST_TICKET_SIGN_IN_URL = "https://ticket.com.tw/application/utk13/utk1306_.aspx" +CONST_URBTIX_SIGN_IN_URL = "https://www.urbtix.hk/member-login" + +CONST_FROM_TOP_TO_BOTTOM = "from top to bottom" +CONST_FROM_BOTTOM_TO_TOP = "from bottom to top" +CONST_CENTER = "center" +CONST_RANDOM = "random" +CONST_SELECT_ORDER_DEFAULT = CONST_FROM_TOP_TO_BOTTOM + +CONT_STRING_1_SEATS_REMAINING = ['@1 seat(s) remaining','剩餘 1@','@1 席残り'] + +CONST_OCR_CAPTCH_IMAGE_SOURCE_NON_BROWSER = "NonBrowser" +CONST_OCR_CAPTCH_IMAGE_SOURCE_CANVAS = "canvas" + +CONST_WEBDRIVER_TYPE_NODRIVER = "nodriver" +CONST_CHROME_FAMILY = ["chrome","edge","brave"] +USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" + +warnings.simplefilter('ignore',InsecureRequestWarning) +ssl._create_default_https_context = ssl._create_unverified_context +logging.basicConfig() +logger = logging.getLogger('logger') + + +def get_config_dict(args): + app_root = util.get_app_root() + config_filepath = os.path.join(app_root, CONST_MAXBOT_CONFIG_FILE) + + # allow assign config by command line. + if 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: + config_dict["advanced"]["headless"] = util.t_or_f(args.headless) + + 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"] = args.tixcraft_sid + if not args.ibonqware is None: + if len(args.ibonqware) > 0: + config_dict["advanced"]["ibonqware"] = args.ibonqware + + 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_plaintext"] = args.kktix_password + + 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_ocr = False + if config_dict["advanced"]["headless"]: + # for tixcraft headless. + #print("If you are runnig headless mode on tixcraft, you need input your cookie SID.") + if len(config_dict["advanced"]["tixcraft_sid"]) > 1: + is_headless_enable_ocr = True + + if is_headless_enable_ocr: + config_dict["ocr_captcha"]["enable"] = True + config_dict["ocr_captcha"]["force_submit"] = True + + return config_dict + +def write_question_to_file(question_text): + working_dir = os.path.dirname(os.path.realpath(__file__)) + target_path = os.path.join(working_dir, CONST_MAXBOT_QUESTION_FILE) + util.write_string_to_file(target_path, question_text) + +def write_last_url_to_file(url): + working_dir = os.path.dirname(os.path.realpath(__file__)) + target_path = os.path.join(working_dir, CONST_MAXBOT_LAST_URL_FILE) + util.write_string_to_file(target_path, url) + +def read_last_url_from_file(): + ret = "" + with open(CONST_MAXBOT_LAST_URL_FILE, "r") as text_file: + ret = text_file.readline() + return ret + +def play_sound_while_ordering(config_dict): + app_root = util.get_app_root() + captcha_sound_filename = os.path.join(app_root, config_dict["advanced"]["play_sound"]["filename"].strip()) + util.play_mp3_async(captcha_sound_filename) + +async def nodriver_kktix_signin(tab, url, config_dict): + kktix_account = config_dict["advanced"]["kktix_account"] + kktix_password = config_dict["advanced"]["kktix_password_plaintext"].strip() + if kktix_password == "": + kktix_password = util.decryptMe(config_dict["advanced"]["kktix_password"]) + if len(kktix_account) > 4: + account = await tab.select("#user_login") + await account.send_keys(kktix_account) + #await tab.sleep(0.1) + + password = await tab.select("#user_password") + await password.send_keys(kktix_password) + #await tab.sleep(0.1) + + submit = await tab.select("input[type='submit'][name]") + await submit.click() + await tab.sleep(0.2) + +async def nodriver_kktix_paused_main(tab, url, config_dict, kktix_dict): + is_url_contain_sign_in = False + # fix https://kktix.com/users/sign_in?back_to=https://kktix.com/events/xxxx and registerStatus: SOLD_OUT cause page refresh. + if '/users/sign_in?' in url: + await nodriver_kktix_signin(tab, url, config_dict) + is_url_contain_sign_in = True + + return kktix_dict + +async def nodriver_goto_homepage(driver, config_dict): + homepage = config_dict["homepage"] + 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) + return await driver.get(homepage) + +async def nodriver_kktix_travel_price_list(tab, 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 = await tab.select_all('div.display-table-row') + except Exception as exc: + ticket_price_list = None + print("find ticket-price Exception:") + print(exc) + pass + + is_dom_ready = True + 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: + is_dom_ready = False + print("find ticket-price fail") + + 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 = util.format_keyword_string(kktix_area_keyword_1) + kktix_area_keyword_1_and = util.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 = "" + row_input = None + current_ticket_number = "0" + try: + js_attr = await row.get_js_attributes() + row_html = js_attr["innerHTML"] + row_text = js_attr["innerText"] + row_input = await row.query_selector("input") + if not row_input is None: + js_attr_input = await row_input.get_js_attributes() + current_ticket_number = js_attr_input["value"] + except Exception as exc: + is_dom_ready = False + 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 not row_input is None: + # check ticket input textbox. + 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 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(row_input) + + # from top to bottom, match first to break. + if kktix_area_auto_select_mode == CONST_FROM_TOP_TO_BOTTOM: + break + + + if not is_dom_ready: + # not sure to break or continue..., maybe break better. + break + else: + if show_debug_message: + print("no any price list found.") + pass + + return is_dom_ready, is_ticket_number_assigned, areas + + +async def nodriver_kktix_assign_ticket_number(tab, config_dict, kktix_area_keyword): + show_debug_message = True # debug. + show_debug_message = False # online + + if config_dict["advanced"]["verbose"]: + show_debug_message = True + + ticket_number_str = str(config_dict["ticket_number"]) + auto_select_mode = config_dict["area_auto_select"]["mode"] + + is_ticket_number_assigned = False + matched_blocks = None + is_dom_ready = True + is_dom_ready, is_ticket_number_assigned, matched_blocks = await nodriver_kktix_travel_price_list(tab, config_dict, auto_select_mode, kktix_area_keyword) + + target_area = None + is_need_refresh = False + if is_dom_ready: + if not is_ticket_number_assigned: + target_area = util.get_target_item_from_matched_list(matched_blocks, auto_select_mode) + + if not matched_blocks is None: + if len(matched_blocks) == 0: + is_need_refresh = True + if show_debug_message: + print("matched_blocks is empty, is_need_refresh") + + if not target_area is None: + current_ticket_number = "" + if show_debug_message: + print("try to get input box value.") + try: + current_ticket_number = str(target_area.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_input() + target_area.send_keys(ticket_number_str) + is_ticket_number_assigned = True + except Exception as exc: + print("asssign ticket number to ticket-price field Exception:") + print(exc) + try: + target_area.clear() + target_area.send_keys("1") + is_ticket_number_assigned = True + except Exception as exc2: + print("asssign ticket number to ticket-price still failed.") + pass + else: + if show_debug_message: + print("value already assigned.") + # already assigned. + is_ticket_number_assigned = True + + + return is_dom_ready, is_ticket_number_assigned, is_need_refresh + +async def nodriver_get_kktix_question_text(tab): + question_text = "" + + captcha_inner_div = None + try: + captcha_inner_div = await tab.select('div.custom-captcha-inner p') + if not captcha_inner_div is None: + js_attr = await captcha_inner_div.get_js_attributes() + question_text = js_attr["innerText"].strip() + except Exception as exc: + print(exc) + pass + return question_text + +# 本票券需要符合以下任一資格才可以購買 +async def nodriver_get_kktix_control_label_text(tab): + question_text = "" + + captcha_inner_div = None + try: + captcha_inner_div = await tab.select('div > div.code-input > div.control-group > label.control-label') + if not captcha_inner_div is None: + js_attr = await captcha_inner_div.get_js_attributes() + question_text = js_attr["innerText"] + except Exception as exc: + pass + return question_text + +async def nodriver_kktix_reg_captcha(tab, config_dict, fail_list, 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 = await nodriver_get_kktix_question_text(tab) + if len(question_text) > 0: + is_question_popup = True + write_question_to_file(question_text) + + answer_list = util.get_answer_list_from_user_guess_string(config_dict, CONST_MAXBOT_ANSWER_ONLINE_FILE) + if len(answer_list)==0: + if config_dict["advanced"]["auto_guess_options"]: + #answer_list = util.get_answer_list_from_question_string(registrationsNewApp_div, question_text) + # due to selenium forat. + answer_list = util.get_answer_list_from_question_string(None, 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("question_text:", question_text) + 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 = '' + submit_by_enter = False + check_input_interval = 0.2 + #is_answer_sent, fail_list = fill_common_verify_form(tab, config_dict, inferred_answer_string, fail_list, input_text_css, next_step_button_css, submit_by_enter, check_input_interval) + if len(answer_list) > 0: + input_text = await tab.select(input_text_css) + if not input_text is None: + await input_text.send_keys(answer_list[0]) + + # due multi next buttons(pick seats/best seats) + await nodriver_kktix_press_next_button(tab) + + return fail_list, is_question_popup + +# : This is for case-2 next button. +async def nodriver_kktix_press_next_button(tab): + ret = False + + css_select = "div.register-new-next-button-area > button" + but_button_list = None + try: + but_button_list = await tab.select_all(css_select) + except Exception as exc: + print(exc) + pass + + if not but_button_list is None: + button_count = len(but_button_list) + #print("button_count:",button_count) + if button_count > 0: + try: + #print("click on last button") + await but_button_list[button_count-1].click() + ret = True + except Exception as exc: + print(exc) + pass + + return ret + + +async def nodriver_kktix_reg_new_main(tab, config_dict, fail_list, played_sound_ticket): + show_debug_message = True # debug. + show_debug_message = False # online + + if config_dict["advanced"]["verbose"]: + show_debug_message = True + + # read config. + area_keyword = config_dict["area_auto_select"]["area_keyword"].strip() + + # part 1: check div. + registrationsNewApp_div = None + try: + registrationsNewApp_div = await tab.select('#registrationsNewApp') + except Exception as exc: + pass + #print("find input fail:", exc) + + # part 2: assign ticket number + is_ticket_number_assigned = False + if not registrationsNewApp_div is None: + is_dom_ready = True + is_need_refresh = False + + if len(area_keyword) > 0: + area_keyword_array = [] + try: + area_keyword_array = json.loads("["+ area_keyword +"]") + except Exception as exc: + area_keyword_array = [] + + # default refresh + is_need_refresh_final = True + + for area_keyword_item in area_keyword_array: + is_need_refresh_tmp = Falses + is_dom_ready, is_ticket_number_assigned, is_need_refresh_tmp = await nodriver_kktix_assign_ticket_number(tab, config_dict, area_keyword_item) + + if not is_dom_ready: + # page redirecting. + break + + # one of keywords not need to refresh, final is not refresh. + if not is_need_refresh_tmp: + is_need_refresh_final = False + + if is_ticket_number_assigned: + break + else: + if show_debug_message: + print("is_need_refresh for keyword:", area_keyword_item) + + if not is_ticket_number_assigned: + is_need_refresh = is_need_refresh_final + else: + # empty keyword, match all. + # TODO: + is_dom_ready, is_ticket_number_assigned, is_need_refresh = await nodriver_kktix_assign_ticket_number(tab, config_dict, "") + pass + + if is_dom_ready: + # part 3: captcha + if is_ticket_number_assigned: + if config_dict["advanced"]["play_sound"]["ticket"]: + if not played_sound_ticket: + play_sound_while_ordering(config_dict) + played_sound_ticket = True + + # whole event question. + fail_list, is_question_popup = await nodriver_kktix_reg_captcha(tab, config_dict, fail_list, registrationsNewApp_div) + + # single option question + if not is_question_popup: + # no captcha text popup, goto next page. + control_text = await nodriver_get_kktix_control_label_text(tab) + if show_debug_message: + print("control_text:", control_text) + + if len(control_text) == 0: + click_ret = await nodriver_kktix_press_next_button(tab) + else: + # input by maxbox plus extension. + is_fill_at_webdriver = False + + if not config_dict["browser"] in CONST_CHROME_FAMILY: + is_fill_at_webdriver = True + else: + if not config_dict["advanced"]["chrome_extension"]: + is_fill_at_webdriver = True + + # TODO: not implement in extension, so force to fill in webdriver. + is_fill_at_webdriver = True + if is_fill_at_webdriver: + #TODO: + #set_kktix_control_label_text(driver, config_dict) + pass + else: + if is_need_refresh: + # reset to play sound when ticket avaiable. + played_sound_ticket = False + + try: + print("no match any price, start to refresh page...") + await tab.reload() + except Exception as exc: + #print("refresh fail") + pass + + if config_dict["advanced"]["auto_reload_page_interval"] > 0: + time.sleep(config_dict["advanced"]["auto_reload_page_interval"]) + + return fail_list, played_sound_ticket + +async def nodriver_kktix_main(tab, url, config_dict, kktix_dict): + is_url_contain_sign_in = False + # fix https://kktix.com/users/sign_in?back_to=https://kktix.com/events/xxxx and registerStatus: SOLD_OUT cause page refresh. + if '/users/sign_in?' in url: + await nodriver_kktix_signin(tab, url, config_dict) + is_url_contain_sign_in = True + + if not is_url_contain_sign_in: + if '/registrations/new' in url: + kktix_dict["start_time"] = time.time() + + is_dom_ready = False + try: + html_body = await tab.get_content() + #print("html_body:",len(html_body)) + if len(html_body) > 10240: + if "registrationsNewApp" in html_body: + if not "{{'new.i_read_and_agree_to'" in html_body: + is_dom_ready = True + except Exception as exc: + print(exc) + pass + + if not is_dom_ready: + # reset answer fail list. + kktix_dict["fail_list"] = [] + kktix_dict["played_sound_ticket"] = False + else: + is_finish_checkbox_click = False + #TODO: check checkbox here. + + # check is able to buy. + if config_dict["kktix"]["auto_fill_ticket_number"]: + kktix_dict["fail_list"], kktix_dict["played_sound_ticket"] = await nodriver_kktix_reg_new_main(tab, config_dict, kktix_dict["fail_list"], kktix_dict["played_sound_ticket"]) + kktix_dict["done_time"] = time.time() + else: + is_event_page = False + if '/events/' in url: + # ex: https://xxx.kktix.cc/events/xxx-copy-1 + if len(url.split('/'))<=5: + is_event_page = True + + if is_event_page: + if config_dict["kktix"]["auto_press_next_step_button"]: + # pass switch check. + #print("should press next here.") + #kktix_events_press_next_button(driver) + pass + + # reset answer fail list. + kktix_dict["fail_list"] = [] + kktix_dict["played_sound_ticket"] = False + + is_kktix_got_ticket = False + if '/events/' in url and '/registrations/' in url and "-" in url: + if not '/registrations/new' in url: + if not 'https://kktix.com/users/sign_in?' in url: + is_kktix_got_ticket = True + + if is_kktix_got_ticket: + if '/events/' in config_dict["homepage"] and '/registrations/' in config_dict["homepage"] and "-" in config_dict["homepage"]: + # do nothing when second time come in. + if len(url.split('/'))>=7: + if len(config_dict["homepage"].split('/'))>=7: + # match event code. + if url.split('/')[4]==config_dict["homepage"].split('/')[4]: + # break loop. + is_kktix_got_ticket = False + + if is_kktix_got_ticket: + if not kktix_dict["start_time"] is None: + if not kktix_dict["done_time"] is None: + bot_elapsed_time = kktix_dict["done_time"] - kktix_dict["start_time"] + if kktix_dict["elapsed_time"] != bot_elapsed_time: + print("bot elapsed time:", "{:.3f}".format(bot_elapsed_time)) + kktix_dict["elapsed_time"] = bot_elapsed_time + + if config_dict["advanced"]["play_sound"]["order"]: + if not kktix_dict["played_sound_order"]: + play_sound_while_ordering(config_dict) + + kktix_dict["played_sound_order"] = True + + if config_dict["advanced"]["headless"]: + if not kktix_dict["is_popup_checkout"]: + kktix_account = config_dict["advanced"]["kktix_account"] + kktix_password = config_dict["advanced"]["kktix_password_plaintext"].strip() + if kktix_password == "": + kktix_password = util.decryptMe(config_dict["advanced"]["kktix_password"]) + + print("基本資料(或實名制)網址:", url) + if len(kktix_account) > 0: + print("搶票成功, 帳號:", kktix_account) + threading.Thread(target=launch_maxbot, args=("", url, kktix_account, kktix_password, "false", )).start() + #driver.quit() + #sys.exit() + + is_event_page = False + if len(url.split('/'))>=7: + is_event_page = True + if is_event_page: + confirm_clicked = False + + try: + submit = await tab.select("div.form-actions a.btn-primary") + await submit.click() + confirm_clicked = True + except Exception as exc: + print(exc) + + if confirm_clicked: + domain_name = url.split('/')[2] + checkout_url = "https://%s/account/orders" % (domain_name) + print("搶票成功, 請前往該帳號訂單查看: %s" % (checkout_url)) + webbrowser.open_new(checkout_url) + + kktix_dict["is_popup_checkout"] = True + driver.quit() + sys.exit() + else: + kktix_dict["is_popup_checkout"] = False + kktix_dict["played_sound_order"] = False + + return kktix_dict + +def launch_maxbot(filename, homepage="", kktix_account = "", kktix_password="", headless="", script_name="nodriver_tixcraft"): + cmd_argument = [] + if len(filename) > 0: + cmd_argument.append('--input=' + filename) + if len(homepage) > 0: + cmd_argument.append('--homepage=' + homepage) + if len(kktix_account) > 0: + cmd_argument.append('--kktix_account=' + kktix_account) + if len(kktix_password) > 0: + cmd_argument.append('--kktix_password=' + kktix_password) + if len(headless) > 0: + cmd_argument.append('--headless=' + headless) + + working_dir = os.path.dirname(os.path.realpath(__file__)) + if hasattr(sys, 'frozen'): + print("execute in frozen mode") + # check platform here. + cmd = './' + script_name + ' '.join(cmd_argument) + if platform.system() == 'Darwin': + print("execute MacOS python script") + if platform.system() == 'Linux': + print("execute linux binary") + if platform.system() == 'Windows': + print("execute .exe binary.") + cmd = script_name + '.exe ' + ' '.join(cmd_argument) + subprocess.Popen(cmd, shell=True, cwd=working_dir) + else: + interpreter_binary = 'python' + interpreter_binary_alt = 'python3' + if platform.system() != 'Windows': + interpreter_binary = 'python3' + interpreter_binary_alt = 'python' + print("execute in shell mode.") + + try: + print('try', interpreter_binary) + cmd_array = [interpreter_binary, script_name + '.py'] + cmd_argument + s=subprocess.Popen(cmd_array, cwd=working_dir) + except Exception as exc: + print('try', interpreter_binary_alt) + try: + cmd_array = [interpreter_binary_alt, script_name + '.py'] + cmd_argument + s=subprocess.Popen(cmd_array, cwd=working_dir) + except Exception as exc: + msg=str(exc) + print("exeption:", msg) + pass + +def get_nodriver_browser_args(): + browser_args = [ + "--user-agent=%s" % (USER_AGENT), + "--remote-allow-origins=*", + "--homepage=about:blank", + "--no-first-run", + "--no-service-autorun", + "--no-default-browser-check", + "--no-pings", + "--no-sandbox" + "--password-store=basic", + "--disable-2d-canvas-clip-aa", + "--disable-3d-apis", + "--disable-animations", + "--disable-app-info-dialog-mac", + "--disable-background-networking", + "--disable-backgrounding-occluded-windows", + "--disable-bookmark-reordering", + "--disable-boot-animation", + "--disable-breakpad", + "--disable-canvas-aa", + "--disable-client-side-phishing-detection", + "--disable-cloud-import", + "--disable-component-cloud-policy", + "--disable-component-update", + "--disable-composited-antialiasing", + "--disable-d3d11", + "--disable-default-apps", + "--disable-dev-shm-usage", + "--disable-device-discovery-notifications", + "--disable-dinosaur-easter-egg", + "--disable-domain-reliability", + "--disable-features=IsolateOrigins,site-per-process,TranslateUI", + "--disable-infobars", + "--disable-logging", + "--disable-login-animations", + "--disable-login-screen-apps", + "--disable-notifications", + "--disable-office-editing-component-extension", + "--disable-password-generation", + "--disable-popup-blocking", + "--disable-renderer-backgrounding", + "--disable-renderer-backgrounding", + "--disable-session-crashed-bubble", + "--disable-smooth-scrolling", + "--disable-sync", + "--disable-translate", + #"--disable-remote-fonts", + "--lang=zh-TW", + ] + return browser_args + +def get_maxbot_plus_extension_path(): + extension_path = "webdriver/Maxbotplus_1.0.0/" + if platform.system() == 'Windows': + extension_path = extension_path.replace("/","\\") + + app_root = util.get_app_root() + config_filepath = os.path.join(app_root, extension_path) + #print("config_filepath:", config_filepath) + + # check extesion mainfest + path = pathlib.Path(config_filepath) + if path.exists(): + if path.is_dir(): + #print("found extension dir") + for item in path.rglob("manifest.*"): + path = item.parent + #print("final path:", path) + + return config_filepath + +def get_extension_config(config_dict): + default_lang = "zh-TW" + conf = Config(browser_args=get_nodriver_browser_args(), lang=default_lang, headless=config_dict["advanced"]["headless"]) + conf.add_extension(get_maxbot_plus_extension_path()) + return conf + +async def nodrver_block_urls(tab, config_dict): + NETWORK_BLOCKED_URLS = ['*/adblock.js' + ,'*/google_ad_block.js' + ,'*google-analytics.*' + ,'*googletagmanager.*' + ,'*googletagservices.*' + ,'*googlesyndication.*' + ,'*play.google.com/*' + ,'*cdn.cookielaw.org/*' + ,'*fundingchoicesmessages.google.com/*' + ,'*.doubleclick.net/*' + ,'*.rollbar.com/*' + ,'*.cloudfront.com/*' + ,'*.lndata.com/*' + ,'*.twitter.com/i/*' + ,'*platform.twitter.com/*' + ,'*syndication.twitter.com/*' + ,'*youtube.com/*' + ,'*player.youku.*' + ,'*.clarity.ms/*' + ,'*img.uniicreative.com/*' + ,'*e2elog.fetnet.net*'] + + if True: + #if config_dict["advanced"]["hide_some_image"]: + NETWORK_BLOCKED_URLS.append('*.woff') + NETWORK_BLOCKED_URLS.append('*.woff2') + NETWORK_BLOCKED_URLS.append('*.ttf') + NETWORK_BLOCKED_URLS.append('*.otf') + NETWORK_BLOCKED_URLS.append('*fonts.googleapis.com/earlyaccess/*') + NETWORK_BLOCKED_URLS.append('*/ajax/libs/font-awesome/*') + NETWORK_BLOCKED_URLS.append('*.ico') + NETWORK_BLOCKED_URLS.append('*ticketimg2.azureedge.net/image/ActivityImage/*') + NETWORK_BLOCKED_URLS.append('*static.tixcraft.com/images/activity/*') + NETWORK_BLOCKED_URLS.append('*static.ticketmaster.sg/images/activity/*') + NETWORK_BLOCKED_URLS.append('*static.ticketmaster.com/images/activity/*') + NETWORK_BLOCKED_URLS.append('*ticketimg2.azureedge.net/image/ActivityImage/ActivityImage_*') + NETWORK_BLOCKED_URLS.append('*.azureedge.net/QWARE_TICKET//images/*') + NETWORK_BLOCKED_URLS.append('*static.ticketplus.com.tw/event/*') + + NETWORK_BLOCKED_URLS.append('https://kktix.cc/change_locale?locale=*') + NETWORK_BLOCKED_URLS.append('https://t.kfs.io/assets/logo_*.png') + NETWORK_BLOCKED_URLS.append('https://t.kfs.io/assets/icon-*.png') + NETWORK_BLOCKED_URLS.append('https://t.kfs.io/upload_images/*.jpg') + + if False: + #if config_dict["advanced"]["block_facebook_network"]: + NETWORK_BLOCKED_URLS.append('*facebook.com/*') + NETWORK_BLOCKED_URLS.append('*.fbcdn.net/*') + + await tab.send(cdp.network.enable()) + # set_blocked_ur_ls is author's typo..., waiting author to chagne. + await tab.send(cdp.network.set_blocked_ur_ls(NETWORK_BLOCKED_URLS)) + return tab + +async def main(args): + config_dict = get_config_dict(args) + + driver = None + tab = None + if not config_dict is None: + conf = get_extension_config(config_dict) + driver = await uc.start(conf) + if not driver is None: + tab = await nodriver_goto_homepage(driver, config_dict) + tab = await nodrver_block_urls(tab, config_dict) + if not config_dict["advanced"]["headless"]: + if len(config_dict["advanced"]["window_size"]) > 0: + if "," in config_dict["advanced"]["window_size"]: + target_array = config_dict["advanced"]["window_size"].split(",") + #tab = await driver.main_tab() + if tab: + await tab.set_window_size(left=30, top=30, width=int(target_array[0]), height=int(target_array[1])) + else: + print("無法使用nodriver,程式無法繼續工作") + sys.exit() + else: + print("Load config error!") + + # internal variable. 說明:這是一個內部變數,請略過。 + url = "" + last_url = "" + + # for tixcraft + tixcraft_dict = {} + tixcraft_dict["fail_list"]=[] + tixcraft_dict["fail_promo_list"]=[] + tixcraft_dict["start_time"]=None + tixcraft_dict["done_time"]=None + tixcraft_dict["elapsed_time"]=None + tixcraft_dict["is_popup_checkout"] = False + tixcraft_dict["area_retry_count"]=0 + + # for kktix + kktix_dict = {} + kktix_dict["fail_list"]=[] + kktix_dict["start_time"]=None + kktix_dict["done_time"]=None + kktix_dict["elapsed_time"]=None + kktix_dict["is_popup_checkout"] = False + kktix_dict["played_sound_ticket"] = False + kktix_dict["played_sound_order"] = False + + fami_dict = {} + fami_dict["fail_list"] = [] + fami_dict["last_activity"]="" + + ibon_dict = {} + ibon_dict["fail_list"]=[] + ibon_dict["start_time"]=None + ibon_dict["done_time"]=None + ibon_dict["elapsed_time"]=None + + hkticketing_dict = {} + hkticketing_dict["is_date_submiting"] = False + hkticketing_dict["fail_list"]=[] + + ticketplus_dict = {} + ticketplus_dict["fail_list"]=[] + ticketplus_dict["is_popup_confirm"] = False + + ocr = None + Captcha_Browser = None + try: + if config_dict["ocr_captcha"]["enable"]: + ocr = ddddocr.DdddOcr(show_ad=False, beta=config_dict["ocr_captcha"]["beta"]) + Captcha_Browser = NonBrowser() + if len(config_dict["advanced"]["tixcraft_sid"]) > 1: + set_non_browser_cookies(driver, config_dict["homepage"], Captcha_Browser) + except Exception as exc: + print(exc) + pass + + maxbot_last_reset_time = time.time() + while True: + time.sleep(0.05) + + # pass if driver not loaded. + if driver is None: + print("nodriver not accessible!") + break + + x_window = None + try: + x_window = await tab.js_dumps('window') + except Exception as exc: + print(exc) + pass + + url = None + if x_window: + if "location" in x_window: + if "href" in x_window["location"]: + url = x_window["location"]["href"] + + if url is None: + continue + else: + if len(url) == 0: + continue + + is_maxbot_paused = False + if os.path.exists(CONST_MAXBOT_INT28_FILE): + is_maxbot_paused = True + + if len(url) > 0 : + if url != last_url: + print(url) + write_last_url_to_file(url) + if is_maxbot_paused: + print("MAXBOT Paused.") + last_url = url + + if is_maxbot_paused: + if 'kktix.c' in url: + kktix_dict = await nodriver_kktix_paused_main(tab, url, config_dict, kktix_dict) + # sleep more when paused. + time.sleep(0.1) + continue + + # for kktix.cc and kktix.com + if 'kktix.c' in url: + kktix_dict = await nodriver_kktix_main(tab, url, config_dict, kktix_dict) + pass + + +def cli(): + parser = argparse.ArgumentParser( + description="MaxBot Aggument Parser") + + parser.add_argument("--input", + help="config file path", + type=str) + + parser.add_argument("--homepage", + help="overwrite homepage setting", + type=str) + + parser.add_argument("--ticket_number", + help="overwrite ticket_number setting", + type=int) + + parser.add_argument("--tixcraft_sid", + help="overwrite tixcraft sid field", + type=str) + + parser.add_argument("--kktix_account", + help="overwrite kktix_account field", + type=str) + + parser.add_argument("--kktix_password", + help="overwrite kktix_password field", + type=str) + + parser.add_argument("--ibonqware", + help="overwrite ibonqware field", + type=str) + + #default="False", + parser.add_argument("--headless", + help="headless mode", + type=str) + + parser.add_argument("--browser", + help="overwrite browser setting", + default='', + choices=['chrome','firefox','edge','safari','brave'], + type=str) + + parser.add_argument("--proxy_server", + help="overwrite proxy server, format: ip:port", + type=str) + + args = parser.parse_args() + uc.loop().run_until_complete(main(args)) + +if __name__ == "__main__": + cli() diff --git a/settings.py b/settings.py index 4cebc50..34a4982 100644 --- a/settings.py +++ b/settings.py @@ -41,7 +41,7 @@ try: except Exception as exc: pass -CONST_APP_VERSION = "MaxBot (2024.03.20)" +CONST_APP_VERSION = "MaxBot (2024.03.21)" CONST_MAXBOT_ANSWER_ONLINE_FILE = "MAXBOT_ONLINE_ANSWER.txt" CONST_MAXBOT_CONFIG_FILE = "settings.json" @@ -1117,7 +1117,13 @@ def btn_run_clicked(language_code): threading.Thread(target=launch_maxbot).start() def launch_maxbot(): - run_python_script("chrome_tixcraft") + global combo_webdriver_type + webdriver_type = combo_webdriver_type.get().strip() + + python_script_name = "chrome_tixcraft" + if webdriver_type == CONST_WEBDRIVER_TYPE_NODRIVER: + python_script_name = "nodriver_tixcraft" + run_python_script(python_script_name) def show_preview_text(): if os.path.exists(CONST_MAXBOT_ANSWER_ONLINE_FILE): @@ -1908,14 +1914,15 @@ def AdvancedTab(root, config_dict, language_code, UI_PADDING_X): browser_options = ("chrome","firefox","edge","safari","brave") webdriver_type_options = (CONST_WEBDRIVER_TYPE_SELENIUM, CONST_WEBDRIVER_TYPE_UC) - not_support_python_version = ["3.7.","3.8."] + not_support_python_version = ["3.6.", "3.7.", "3.8."] is_current_version_after_3_9 = True - for ver in not_support_python_version: - if ver in platform.python_version(): + for not_support_ver in not_support_python_version: + current_version = platform.python_version() + if current_version[:4] == not_support_ver : is_current_version_after_3_9 = False break if is_current_version_after_3_9: - #webdriver_type_options = (CONST_WEBDRIVER_TYPE_SELENIUM, CONST_WEBDRIVER_TYPE_UC, CONST_WEBDRIVER_TYPE_NODRIVER) + webdriver_type_options = (CONST_WEBDRIVER_TYPE_SELENIUM, CONST_WEBDRIVER_TYPE_UC, CONST_WEBDRIVER_TYPE_NODRIVER) pass row_count = 0 diff --git a/util.py b/util.py index caeeab1..f57b4e9 100644 --- a/util.py +++ b/util.py @@ -1426,6 +1426,433 @@ def check_answer_keep_symbol(captcha_text_div_text): return is_need_keep_symbol +#PS: this is for selenium webdriver. +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 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 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_get_registerStatus(event_code): html_result = None