From 316fc727d8c7e18769ee3ecbb9555ae4173a24d0 Mon Sep 17 00:00:00 2001 From: CHUN YU YAO Date: Mon, 13 Mar 2023 00:34:59 +0800 Subject: [PATCH] 2023-03-11, add new feature to pause MAXBOT. fix bugs for ibon. --- chrome_tixcraft.py | 394 ++++++++++++++++++++++++++++++++------------- config_launcher.py | 13 +- settings.py | 193 +++++++++++++++++++--- 3 files changed, 467 insertions(+), 133 deletions(-) diff --git a/chrome_tixcraft.py b/chrome_tixcraft.py index 1b9c88e..102c3d0 100644 --- a/chrome_tixcraft.py +++ b/chrome_tixcraft.py @@ -53,7 +53,11 @@ import argparse import ssl ssl._create_default_https_context = ssl._create_unverified_context -CONST_APP_VERSION = u"MaxBot (2023.03.08)" +CONST_APP_VERSION = u"MaxBot (2023.03.11)" + +CONST_MAXBOT_CONFIG_FILE = "settings.json" +CONST_MAXBOT_LAST_URL_FILE = "MAXBOT_LAST_URL.txt" +CONST_MAXBOT_INT28_FILE = "MAXBOT_INT28_IDLE.txt" CONST_HOMEPAGE_DEFAULT = "https://tixcraft.com" URL_GOOGLE_OAUTH = 'https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?redirect_uri=https%3A%2F%2Fdevelopers.google.com%2Foauthplayground&prompt=consent&response_type=code&client_id=407408718192.apps.googleusercontent.com&scope=email&access_type=offline&flowName=GeneralOAuthFlow' @@ -84,6 +88,7 @@ CONST_WEBDRIVER_TYPE_SELENIUM = "selenium" #CONST_WEBDRIVER_TYPE_STEALTH = "stealth" CONST_WEBDRIVER_TYPE_UC = "undetected_chromedriver" + def t_or_f(arg): ret = False ua = str(arg).upper() @@ -120,9 +125,8 @@ def get_app_root(): return app_root def get_config_dict(args): - config_json_filename = 'settings.json' app_root = get_app_root() - config_filepath = os.path.join(app_root, config_json_filename) + config_filepath = os.path.join(app_root, CONST_MAXBOT_CONFIG_FILE) # allow assign config by command line. if not args.input is None: @@ -178,6 +182,16 @@ def get_config_dict(args): config_dict["ocr_captcha"]["force_submit"] = True return config_dict +def write_last_url_to_file(url): + with open(CONST_MAXBOT_LAST_URL_FILE, "w") as text_file: + text_file.write("%s" % 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: @@ -1718,9 +1732,10 @@ def tixcraft_area_auto_select(driver, url, config_dict): # only when keyword#2 filled to query. if area_keyword_2_enable: - is_need_refresh, areas = get_tixcraft_target_area(el, area_keyword_2, area_auto_select_mode, pass_1_seat_remaining_enable) - if show_debug_message: - print("is_need_refresh for keyword2:", is_need_refresh) + if area_keyword_1 != area_keyword_2: + is_need_refresh, areas = get_tixcraft_target_area(el, area_keyword_2, area_auto_select_mode, pass_1_seat_remaining_enable) + if show_debug_message: + print("is_need_refresh for keyword2:", is_need_refresh) if is_need_refresh: if areas is None: @@ -1729,9 +1744,10 @@ def tixcraft_area_auto_select(driver, url, config_dict): # only when keyword#3 filled to query. if area_keyword_3_enable: - is_need_refresh, areas = get_tixcraft_target_area(el, area_keyword_3, area_auto_select_mode, pass_1_seat_remaining_enable) - if show_debug_message: - print("is_need_refresh for keyword3:", is_need_refresh) + if area_keyword_1 != area_keyword_3: + is_need_refresh, areas = get_tixcraft_target_area(el, area_keyword_3, area_auto_select_mode, pass_1_seat_remaining_enable) + if show_debug_message: + print("is_need_refresh for keyword3:", is_need_refresh) if is_need_refresh: if areas is None: @@ -1740,9 +1756,10 @@ def tixcraft_area_auto_select(driver, url, config_dict): # only when keyword#4 filled to query. if area_keyword_4_enable: - is_need_refresh, areas = get_tixcraft_target_area(el, area_keyword_4, area_auto_select_mode, pass_1_seat_remaining_enable) - if show_debug_message: - print("is_need_refresh for keyword4:", is_need_refresh) + if area_keyword_1 != area_keyword_4: + is_need_refresh, areas = get_tixcraft_target_area(el, area_keyword_4, area_auto_select_mode, pass_1_seat_remaining_enable) + if show_debug_message: + print("is_need_refresh for keyword4:", is_need_refresh) area_target = None if areas is not None: @@ -5133,10 +5150,15 @@ def ibon_activity_info(driver, config_dict): return is_date_assign_by_bot -def ibon_area_auto_select(driver, area_auto_select_mode, area_keyword_1, area_keyword_1_and): +def ibon_area_auto_select(driver, config_dict, area_keyword_1, area_keyword_1_and): show_debug_message = True # debug. show_debug_message = False # online + area_auto_select_mode = config_dict["tixcraft"]["area_auto_select"]["mode"] + + if config_dict["advanced"]["verbose"]: + show_debug_message = True + is_price_assign_by_bot = False is_need_refresh = False @@ -5179,9 +5201,22 @@ def ibon_area_auto_select(driver, area_auto_select_mode, area_keyword_1, area_ke if row_is_enabled: try: button_class_string = str(row.get_attribute('class')) - if len(button_class_string) > 1: - if 'disabled' in button_class_string: - row_is_enabled=False + if not button_class_string is None: + if len(button_class_string) > 1: + if 'disabled' in button_class_string: + row_is_enabled=False + if 'sold-out' in button_class_string: + row_is_enabled=False + except Exception as exc: + pass + + if row_is_enabled: + row_is_enabled = False + try: + row_id_string = str(row.get_attribute('id')) + if not row_id_string is None: + if len(row_id_string) > 1: + row_is_enabled = True except Exception as exc: pass @@ -5284,7 +5319,7 @@ def ibon_area_auto_select(driver, area_auto_select_mode, area_keyword_1, area_ke else: is_need_refresh = True if show_debug_message: - print("matched_blocks is empty.") + print("matched_blocks is empty, is_need_refresh") if target_area is not None: try: @@ -5309,13 +5344,15 @@ def ibon_performance(driver, config_dict): show_debug_message = True # debug. show_debug_message = False # online + if config_dict["advanced"]["verbose"]: + show_debug_message = True + is_price_assign_by_bot = False is_need_refresh = False auto_fill_ticket_number = True if auto_fill_ticket_number: # click price row. - area_auto_select_mode = config_dict["tixcraft"]["area_auto_select"]["mode"] area_keyword_1 = config_dict["tixcraft"]["area_auto_select"]["area_keyword_1"].strip() area_keyword_2 = config_dict["tixcraft"]["area_auto_select"]["area_keyword_2"].strip() area_keyword_3 = config_dict["tixcraft"]["area_auto_select"]["area_keyword_3"].strip() @@ -5337,25 +5374,28 @@ def ibon_performance(driver, config_dict): is_need_refresh = False if not is_price_assign_by_bot: - is_need_refresh, is_price_assign_by_bot = ibon_area_auto_select(driver, area_auto_select_mode, area_keyword_1, area_keyword_1_and) + is_need_refresh, is_price_assign_by_bot = ibon_area_auto_select(driver, config_dict, area_keyword_1, area_keyword_1_and) if is_need_refresh: if area_keyword_2_enable: - is_need_refresh, is_price_assign_by_bot = ibon_area_auto_select(driver, area_auto_select_mode, area_keyword_2, area_keyword_2_and) - if show_debug_message: - print("is_need_refresh for keyword2:", is_need_refresh) + if area_keyword_1 != area_keyword_2: + is_need_refresh, is_price_assign_by_bot = ibon_area_auto_select(driver, config_dict, area_keyword_2, area_keyword_2_and) + if show_debug_message: + print("is_need_refresh for keyword2:", is_need_refresh) if is_need_refresh: if area_keyword_3_enable: - is_need_refresh, is_price_assign_by_bot = ibon_area_auto_select(driver, area_auto_select_mode, area_keyword_3, area_keyword_3_and) - if show_debug_message: - print("is_need_refresh for keyword3:", is_need_refresh) + if area_keyword_1 != area_keyword_3: + is_need_refresh, is_price_assign_by_bot = ibon_area_auto_select(driver, config_dict, area_keyword_3, area_keyword_3_and) + if show_debug_message: + print("is_need_refresh for keyword3:", is_need_refresh) if is_need_refresh: if area_keyword_4_enable: - is_need_refresh, is_price_assign_by_bot = ibon_area_auto_select(driver, area_auto_select_mode, area_keyword_4, area_keyword_4_and) - if show_debug_message: - print("is_need_refresh for keyword4:", is_need_refresh) + if area_keyword_1 != area_keyword_4: + is_need_refresh, is_price_assign_by_bot = ibon_area_auto_select(driver, config_dict, area_keyword_4, area_keyword_4_and) + if show_debug_message: + print("is_need_refresh for keyword4:", is_need_refresh) if is_need_refresh: try: @@ -6179,40 +6219,163 @@ def cityline_main(driver, url, config_dict): if len(url.split('/'))>=5: cityline_shows_goto_cta(driver) +def guess_ibon_question(driver): + show_debug_message = True # debug. + show_debug_message = False # online + + inferred_answer_string = None + answer_list = [] + + form_select = None + try: + form_select = driver.find_element(By.CSS_SELECTOR, 'div.editor-box > div > div.form-group > label') + except Exception as exc: + print("find verify textbox fail") + pass + + question_text = None + if form_select is not None: + try: + question_text = form_select.text + except Exception as exc: + print("get text fail") + + is_options_in_question = False + + if inferred_answer_string is None: + if not question_text is None: + inferred_answer_string, answer_list = get_answer_list_from_question_string(None, question_text) + + return inferred_answer_string, answer_list + def ibon_verification_question(driver, answer_index, config_dict): show_debug_message = True # debug. show_debug_message = False # online - user_guess_string = config_dict["kktix"]["user_guess_string"] + if config_dict["advanced"]["verbose"]: + show_debug_message = True - # part 1: check div. - question_div = None - try: - question_div = driver.find_element(By.CSS_SELECTOR, '') - except Exception as exc: - pass - #print("find input fail:", exc) + presale_code = config_dict["tixcraft"]["presale_code"] + presale_code_delimiter = config_dict["tixcraft"]["presale_code_delimiter"] - #captcha_text_div_text - captcha_text_div_text = None + inferred_answer_string = None + answer_list = [] - captcha_password_inputbox = None - try: - captcha_password_inputbox = driver.find_element(By.CSS_SELECTOR, '') - except Exception as exc: - pass + is_retry_user_single_answer = False - if not captcha_password_inputbox is None: - inferred_answer_string = None - - if len(user_guess_string) > 0: - inferred_answer_string = user_guess_string + if len(presale_code) > 0: + if len(presale_code_delimiter) > 0: + if presale_code_delimiter in presale_code: + answer_list = presale_code.split(presale_code_delimiter) + if len(answer_list) > 0: + if answer_index < len(answer_list)-1: + inferred_answer_string = answer_list[answer_index+1] else: - if not captcha_text_div_text is None: - inferred_answer_string, answer_list = get_answer_list_from_question_string(None, captcha_text_div_text) + is_retry_user_single_answer = True + if answer_index < 2: + inferred_answer_string = presale_code + + if inferred_answer_string is None: + inferred_answer_string, answer_list = guess_ibon_question(driver) + if inferred_answer_string is None: + if not answer_list is None: + if len(answer_list) > 0: + if answer_index < len(answer_list)-1: + inferred_answer_string = answer_list[answer_index+1] + + if show_debug_message: + print("answer_index:", answer_index) + print("inferred_answer_string:", inferred_answer_string) + print("answer_index:", answer_index) + print("is_retry_user_single_answer:", is_retry_user_single_answer) + + form_input = None + try: + form_input = driver.find_element(By.CSS_SELECTOR, 'div.editor-box > div > div.form-group > input') + except Exception as exc: + print("find verify code fail") + pass + + inputed_value = None + if form_input is not None: + try: + inputed_value = form_input.get_attribute('value') + except Exception as exc: + print("find verify code fail") + pass + + if inputed_value is None: + inputed_value = "" + + if not inferred_answer_string is None: + is_password_sent = False + if len(inputed_value)==0: + try: + # PS: sometime may send key twice... + form_input.clear() + form_input.send_keys(inferred_answer_string) + is_button_clicked = force_press_button(driver, By.CSS_SELECTOR,'div.editor-box > div > div.form-group > a.btn') + is_password_sent = True + + # guess answer mode. + answer_index += 1 + + if show_debug_message: + print("sent password by bot:", inferred_answer_string) + except Exception as exc: + pass + else: + if inputed_value == inferred_answer_string: + if show_debug_message: + print("sent password by previous time.") + is_password_sent = True + try: + form_input.send_keys(Keys.ENTER) + except Exception as exc: + pass + + if is_retry_user_single_answer: + # increase counter for waiting for stop retry. + answer_index += 1 + else: + # guess answer mode. + if answer_index > -1: + # here not is first option. + inferred_answer_previous = None + if answer_index < len(answer_list)-1: + inferred_answer_previous = answer_list[answer_index] + if inputed_value == inferred_answer_previous: + try: + form_input.clear() + form_input.send_keys(inferred_answer_string) + is_button_clicked = force_press_button(driver, By.CSS_SELECTOR,'div.editor-box > div > div.form-group > a.btn') + is_password_sent = True + if show_debug_message: + print("sent password by bot:", inferred_answer_string, "at index:", answer_index+2) + + answer_index += 1 + except Exception as exc: + pass + + if is_password_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: + if len(inputed_value)==0: + try: + form_input.click() + except Exception as exc: + pass return answer_index + def ibon_ticket_agree(driver): # check agree form_checkbox = None @@ -6278,70 +6441,77 @@ def ibon_main(driver, url, config_dict, ibon_dict): if is_event_page: ibon_auto_signup(driver) - #https://ticket.ibon.com.tw/ActivityInfo/Details/0000?pattern=entertainment - if '/ActivityInfo/Details/' in url: - is_event_page = False - if len(url.split('/'))==6: - is_event_page = True + is_match_target_feature = False - if is_event_page: - date_auto_select_enable = config_dict["tixcraft"]["date_auto_select"]["enable"] - if date_auto_select_enable: - ibon_activity_info(driver, config_dict) + if not is_match_target_feature: + #https://ticket.ibon.com.tw/ActivityInfo/Details/0000?pattern=entertainment + if '/ActivityInfo/Details/' in url: + is_event_page = False + if len(url.split('/'))==6: + is_event_page = True - # validation question url: - # https://orders.ibon.com.tw/application/UTK02/UTK0201_0.aspx?rn=1180872370&PERFORMANCE_ID=B04M7XZT&PRODUCT_ID=B04KS88E&SHOW_PLACE_MAP=True - if '/application/UTK02/' in url and '.aspx?rn=' in url: - #PS: not sure, use 'kktix' block or 'tixcraft' block for this feature. - #auto_guess_options = config_dict["kktix"]["auto_guess_options"] - auto_guess_options = True - if auto_guess_options: - #ibon_dict["answer_index"] = ibon_verification_question(driver, ibon_dict["answer_index"], config_dict) - pass - else: - ibon_dict["answer_index"] = -1 + if is_event_page: + date_auto_select_enable = config_dict["tixcraft"]["date_auto_select"]["enable"] + if date_auto_select_enable: + is_match_target_feature = True + ibon_activity_info(driver, config_dict) - # https://orders.ibon.com.tw/application/UTK02/UTK0201_000.aspx?PERFORMANCE_ID=0000 - if '/application/UTK02/' in url and '.aspx?PERFORMANCE_ID=' in url: - is_event_page = False - if len(url.split('/'))==6: - is_event_page = True + if not is_match_target_feature: + # validation question url: + # https://orders.ibon.com.tw/application/UTK02/UTK0201_0.aspx?rn=1180872370&PERFORMANCE_ID=B04M7XZT&PRODUCT_ID=B04KS88E&SHOW_PLACE_MAP=True + if '/application/UTK02/' in url and '.aspx?rn=' in url: + is_match_target_feature = True + ibon_dict["answer_index"] = ibon_verification_question(driver, ibon_dict["answer_index"], config_dict) + else: + ibon_dict["answer_index"] = -1 - if is_event_page: - area_auto_select_enable = config_dict["tixcraft"]["area_auto_select"]["enable"] - if area_auto_select_enable: - if 'PERFORMANCE_PRICE_AREA_ID=' in url: - # step 2: assign ticket number. - ticket_number = str(config_dict["ticket_number"]) - is_ticket_number_assigned = ibon_ticket_number_auto_select(driver, ticket_number) - if is_ticket_number_assigned: - click_ret = ibon_purchase_button_press(driver) - else: - is_sold_out = ibon_check_sold_out(driver) - if is_sold_out: - #is_button_clicked = force_press_button(driver, By.CSS_SELECTOR, 'a.btn.btn-primary') - driver.back() - driver.refresh() - else: - # step 1: select area. - ibon_performance(driver, config_dict) - #https://orders.ibon.com.tw/application/UTK02/UTK0206_.aspx - if 'orders.ibon.com.tw/application/UTK02/UTK020' in url and '.aspx' in url: - is_event_page = False - if len(url.split('/'))==6: - is_event_page = True + if not is_match_target_feature: + # https://orders.ibon.com.tw/application/UTK02/UTK0201_000.aspx?PERFORMANCE_ID=0000 + if '/application/UTK02/' in url and '.aspx?PERFORMANCE_ID=' in url: + is_event_page = False + if len(url.split('/'))==6: + is_event_page = True - if is_event_page: - auto_check_agree = config_dict["auto_check_agree"] - if auto_check_agree: - is_finish_checkbox_click = False - for i in range(3): - is_finish_checkbox_click = ibon_ticket_agree(driver) + if is_event_page: + area_auto_select_enable = config_dict["tixcraft"]["area_auto_select"]["enable"] + if area_auto_select_enable: + if 'PERFORMANCE_PRICE_AREA_ID=' in url: + # step 2: assign ticket number. + is_match_target_feature = True + ticket_number = str(config_dict["ticket_number"]) + is_ticket_number_assigned = ibon_ticket_number_auto_select(driver, ticket_number) + if is_ticket_number_assigned: + click_ret = ibon_purchase_button_press(driver) + else: + is_sold_out = ibon_check_sold_out(driver) + if is_sold_out: + #is_button_clicked = force_press_button(driver, By.CSS_SELECTOR, 'a.btn.btn-primary') + driver.back() + driver.refresh() + + if 'PRODUCT_ID=' in url: + # step 1: select area. + is_match_target_feature = True + ibon_performance(driver, config_dict) + + if not is_match_target_feature: + #https://orders.ibon.com.tw/application/UTK02/UTK0206_.aspx + if 'orders.ibon.com.tw/application/UTK02/UTK020' in url and '.aspx' in url: + is_event_page = False + if len(url.split('/'))==6: + is_event_page = True + + if is_event_page: + auto_check_agree = config_dict["auto_check_agree"] + if auto_check_agree: + is_finish_checkbox_click = False + for i in range(3): + is_finish_checkbox_click = ibon_ticket_agree(driver) + if is_finish_checkbox_click: + break if is_finish_checkbox_click: - break - if is_finish_checkbox_click: - is_button_clicked = force_press_button(driver, By.CSS_SELECTOR, 'a.btn.btn-pink.continue') + is_button_clicked = force_press_button(driver, By.CSS_SELECTOR, 'a.btn.btn-pink.continue') return ibon_dict @@ -8799,8 +8969,15 @@ def main(args): if len(url) > 0 : if url != last_url: print(url) + write_last_url_to_file(url) + if os.path.exists(CONST_MAXBOT_INT28_FILE): + print("MAXBOT Paused.") last_url = url + if os.path.exists(CONST_MAXBOT_INT28_FILE): + time.sleep(0.2) + continue + tixcraft_family = False if 'tixcraft.com' in url: tixcraft_family = True @@ -8916,7 +9093,8 @@ if __name__ == "__main__": #captcha_text_div_text = "以下哪位不是LOVELYZ成員? (請以半形輸入選項內的英文及數字,大小寫須符合),範例:E5e。 (A1a)智愛 (B2b)美珠 (C3c)JON (D4d)叡仁" #captcha_text_div_text = "題請問此次 RAVI的SOLO專輯名稱為?(請以半形輸入法作答,大小寫需要一模一樣,範例:Tt) Aa [ BOOK] 、 Bb [OOK BOOK.R] 、 Cc [R.OOK BOOK] 、 Dd [OOK R. BOOK]" #captcha_text_div_text = "請問下列哪個選項皆為河成雲的創作歌曲? Aa) Don’t Forget、Candle Bb) Don’t Forget、Forever+1 Cc) Don’t Forget、Flowerbomb Dd) Don’t Forget、One Love 請以半形輸入,大小寫含括號需一模一樣 【範例:答案為B需填入Bb)】" - captcha_text_div_text = "魏如萱得過什麼獎?(1) 金馬獎 最佳女主角(2) 金鐘獎 戲劇節目女主角(3) 金曲獎 最佳華語女歌手(4) 走鐘獎 好好聽音樂獎 (請輸入半形數字)" + #captcha_text_div_text = "魏如萱得過什麼獎?(1) 金馬獎 最佳女主角(2) 金鐘獎 戲劇節目女主角(3) 金曲獎 最佳華語女歌手(4) 走鐘獎 好好聽音樂獎 (請輸入半形數字)" + captcha_text_div_text = "Love in the Air 是由哪兩本小說改篇而成呢?(A)Love Strom & Love Sky (B)Love Rain & Love Cloud (C)Love Wind & Love Sun (D)Love Dry & Love Cold (請輸入選項大寫英文單字 範例:E)" inferred_answer_string, answer_list = get_answer_list_from_question_string(None, captcha_text_div_text) print("inferred_answer_string:", inferred_answer_string) print("answer_list:", answer_list) diff --git a/config_launcher.py b/config_launcher.py index d44364e..8b0c829 100644 --- a/config_launcher.py +++ b/config_launcher.py @@ -20,9 +20,10 @@ import json import webbrowser import base64 -CONST_APP_VERSION = u"MaxBot (2023.03.08)" +CONST_APP_VERSION = u"MaxBot (2023.03.11)" -CONST_LAUNCHER_CONFIG_FILENAME = "config_launcher.json" +CONST_MAXBOT_LAUNCHER_FILE = "config_launcher.json" +CONST_MAXBOT_CONFIG_FILE = "settings.json" translate={} @@ -146,7 +147,7 @@ def get_app_root(): def get_default_config(): config_dict={} - config_dict["list"] = ["settings.json"] + config_dict["list"] = [CONST_MAXBOT_CONFIG_FILE] config_dict["advanced"] = {} config_dict["advanced"]["language"] = "English" @@ -158,7 +159,7 @@ def load_json(): app_root = get_app_root() # overwrite config path. - config_filepath = os.path.join(app_root, CONST_LAUNCHER_CONFIG_FILENAME) + config_filepath = os.path.join(app_root, CONST_MAXBOT_LAUNCHER_FILE) config_dict = None if os.path.isfile(config_filepath): @@ -170,7 +171,7 @@ def load_json(): def btn_restore_defaults_clicked(language_code): app_root = get_app_root() - config_filepath = os.path.join(app_root, CONST_LAUNCHER_CONFIG_FILENAME) + config_filepath = os.path.join(app_root, CONST_MAXBOT_LAUNCHER_FILE) config_dict = get_default_config() import json @@ -186,7 +187,7 @@ def btn_save_clicked(language_code): def btn_save_act(language_code, slience_mode=True): app_root = get_app_root() - config_filepath = os.path.join(app_root, CONST_LAUNCHER_CONFIG_FILENAME) + config_filepath = os.path.join(app_root, CONST_MAXBOT_LAUNCHER_FILE) config_dict = get_default_config() diff --git a/settings.py b/settings.py index 6f47129..615469d 100644 --- a/settings.py +++ b/settings.py @@ -20,10 +20,14 @@ import json import webbrowser import pyperclip import base64 +import time +import threading -CONST_APP_VERSION = u"MaxBot (2023.03.08)" +CONST_APP_VERSION = u"MaxBot (2023.03.11)" -CONST_SETTINGS_CONFIG_FILENAME = "settings.json" +CONST_MAXBOT_CONFIG_FILE = "settings.json" +CONST_MAXBOT_LAST_URL_FILE = "MAXBOT_LAST_URL.txt" +CONST_MAXBOT_INT28_FILE = "MAXBOT_INT28_IDLE.txt" CONST_FROM_TOP_TO_BOTTOM = u"from top to bottom" CONST_FROM_BOTTOM_TO_TOP = u"from bottom to top" @@ -83,7 +87,7 @@ def load_translate(): en_us["date_keyword"] = 'Date Keyword' en_us["pass_date_is_sold_out"] = 'Pass date is sold out' en_us["auto_reload_coming_soon_page"] = 'Reload coming soon page' - + en_us["area_auto_select"] = 'Area Auto Select' #en_us["area_select_order"] = 'Area select order' en_us["area_keyword_1"] = 'Area Keyword #1' @@ -98,10 +102,20 @@ def load_translate(): en_us["headless"] = 'Headless mode' # Make the operation more talkative en_us["verbose"] = 'Verbose mode' + en_us["running_status"] = 'Running Status' + en_us["running_url"] = 'Running URL' + en_us["status_idle"] = 'Idle' + en_us["status_paused"] = 'Paused' + en_us["status_enabled"] = 'Enabled' + en_us["status_running"] = 'Running' + + en_us["idle"] = 'Idle' + en_us["resume"] = 'Resume' en_us["preference"] = 'Preference' en_us["advanced"] = 'Advanced' en_us["autofill"] = 'Autofill' + en_us["runtime"] = 'Runtime' en_us["about"] = 'About' en_us["run"] = 'Run' @@ -128,7 +142,7 @@ def load_translate(): en_us["hkticketing_password"] = 'HKTICKETING password' en_us["kham_password"] = 'KHAM password' en_us["save_password_alert"] = 'Saving passwords to config file may expose your passwords.' - + en_us["play_captcha_sound"] = 'Play sound when captcha' en_us["captcha_sound_filename"] = 'captcha sound filename' en_us["adblock_plus_enable"] = 'Adblock Plus Extension' @@ -178,10 +192,20 @@ def load_translate(): zh_tw["webdriver_type"] = 'WebDriver類別' zh_tw["headless"] = '無圖形界面模式' zh_tw["verbose"] = '輸出詳細除錯訊息' + zh_tw["running_status"] = '執行狀態' + zh_tw["running_url"] = '執行網址' + zh_tw["status_idle"] = '閒置中' + zh_tw["status_paused"] = '已暫停' + zh_tw["status_enabled"] = '已啟用' + zh_tw["status_running"] = '執行中' + + zh_tw["idle"] = '暫停搶票' + zh_tw["resume"] = '接續搶票' zh_tw["preference"] = '偏好設定' zh_tw["advanced"] = '進階設定' zh_tw["autofill"] = '自動填表單' + zh_tw["runtime"] = '執行階段' zh_tw["about"] = '關於' zh_tw["run"] = '搶票' @@ -208,7 +232,7 @@ def load_translate(): zh_tw["hkticketing_password"] = 'HKTICKETING 密碼' zh_tw["kham_password"] = '寬宏 密碼' zh_tw["save_password_alert"] = '將密碼保存到設定檔中可能會讓您的密碼被盜。' - + zh_tw["play_captcha_sound"] = '輸入驗證碼時播放音效' zh_tw["captcha_sound_filename"] = '驗證碼用音效檔' zh_tw["adblock_plus_enable"] = 'Adblock 瀏覽器擴充功能' @@ -258,10 +282,20 @@ def load_translate(): zh_cn["webdriver_type"] = 'WebDriver类别' zh_cn["headless"] = '无图形界面模式' zh_cn["verbose"] = '输出详细除错讯息' + zh_cn["running_status"] = '執行狀態' + zh_cn["running_url"] = '執行網址' + zh_cn["status_idle"] = '閒置中' + zh_cn["status_paused"] = '已暫停' + zh_cn["status_enabled"] = '已启用' + zh_cn["status_running"] = '執行中' + + zh_cn["idle"] = '暂停抢票' + zh_cn["resume"] = '接续抢票' zh_cn["preference"] = '偏好设定' zh_cn["advanced"] = '進階設定' zh_cn["autofill"] = '自动填表单' + zh_cn["runtime"] = '运行' zh_cn["about"] = '关于' zh_cn["copy"] = '复制' @@ -289,7 +323,7 @@ def load_translate(): zh_cn["hkticketing_password"] = 'HKTICKETING 密码' zh_cn["kham_password"] = '宽宏 密码' zh_cn["save_password_alert"] = '將密碼保存到文件中可能會暴露您的密碼。' - + zh_cn["play_captcha_sound"] = '输入验证码时播放音效' zh_cn["captcha_sound_filename"] = '验证码用音效档' zh_cn["adblock_plus_enable"] = 'Adblock 浏览器扩充功能' @@ -339,10 +373,20 @@ def load_translate(): ja_jp["webdriver_type"] = 'WebDriverタイプ' ja_jp["headless"] = 'ヘッドレスモード' ja_jp["verbose"] = '詳細モード' + ja_jp["running_status"] = 'スターテス' + ja_jp["running_url"] = '現在の URL' + ja_jp["status_idle"] = '閒置中' + ja_jp["status_paused"] = '一時停止' + ja_jp["status_enabled"] = '有効' + ja_jp["status_running"] = 'ランニング' + + ja_jp["idle"] = 'アイドル' + ja_jp["resume"] = '再開する' ja_jp["preference"] = '設定' ja_jp["advanced"] = '高度な設定' ja_jp["autofill"] = 'オートフィル' + ja_jp["runtime"] = 'ランタイム' ja_jp["about"] = '情報' ja_jp["run"] = 'チケットを取る' @@ -493,11 +537,17 @@ def get_default_config(): return config_dict +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 load_json(): app_root = get_app_root() # overwrite config path. - config_filepath = os.path.join(app_root, CONST_SETTINGS_CONFIG_FILENAME) + config_filepath = os.path.join(app_root, CONST_MAXBOT_CONFIG_FILE) config_dict = None if os.path.isfile(config_filepath): @@ -509,7 +559,7 @@ def load_json(): def btn_restore_defaults_clicked(language_code): app_root = get_app_root() - config_filepath = os.path.join(app_root, CONST_SETTINGS_CONFIG_FILENAME) + config_filepath = os.path.join(app_root, CONST_MAXBOT_CONFIG_FILE) config_dict = get_default_config() import json @@ -520,6 +570,27 @@ def btn_restore_defaults_clicked(language_code): global root load_GUI(root, config_dict) +def btn_idle_clicked(language_code): + app_root = get_app_root() + idle_filepath = os.path.join(app_root, CONST_MAXBOT_INT28_FILE) + with open(CONST_MAXBOT_INT28_FILE, "w") as text_file: + text_file.write("") + update_maxbot_runtime_status(language_code) + +def btn_resume_clicked(language_code): + app_root = get_app_root() + idle_filepath = os.path.join(app_root, CONST_MAXBOT_INT28_FILE) + for i in range(10): + if os.path.exists(idle_filepath): + try: + os.remove(idle_filepath) + break + except Exception as exc: + pass + else: + break + update_maxbot_runtime_status(language_code) + def btn_launcher_clicked(language_code): import subprocess @@ -574,7 +645,7 @@ def btn_save_clicked(language_code): def btn_save_act(language_code, slience_mode=False): app_root = get_app_root() - config_filepath = os.path.join(app_root, CONST_SETTINGS_CONFIG_FILENAME) + config_filepath = os.path.join(app_root, CONST_MAXBOT_CONFIG_FILE) config_dict = get_default_config() @@ -744,7 +815,7 @@ def btn_save_act(language_code, slience_mode=False): config_dict["advanced"]["adblock_plus_enable"] = bool(chk_state_adblock_plus.get()) config_dict["advanced"]["open_google_oauth_url"] = bool(chk_state_google_oauth.get()) - + config_dict["ocr_captcha"] = {} config_dict["ocr_captcha"]["enable"] = bool(chk_state_ocr_captcha.get()) config_dict["ocr_captcha"]["force_submit"] = bool(chk_state_ocr_captcha_force_submit.get()) @@ -759,7 +830,7 @@ def btn_save_act(language_code, slience_mode=False): if not slience_mode: #messagebox.showinfo(translate[language_code]["save"], translate[language_code]["done"]) - file_to_save = asksaveasfilename(initialfile = CONST_SETTINGS_CONFIG_FILENAME, defaultextension=".json",filetypes=[("json Documents","*.json"),("All Files","*.*")]) + file_to_save = asksaveasfilename(initialfile = CONST_MAXBOT_CONFIG_FILE, defaultextension=".json",filetypes=[("json Documents","*.json"),("All Files","*.*")]) if not file_to_save is None: print("save as to:", file_to_save) with open(str(file_to_save), 'w') as outfile: @@ -833,7 +904,6 @@ def btn_preview_sound_clicked(): play_mp3_async(new_sound_filename) def play_mp3_async(sound_filename): - import threading threading.Thread(target=play_mp3, args=(sound_filename,), daemon=True).start() def play_mp3(sound_filename): @@ -981,7 +1051,7 @@ def applyNewLanguage(): lbl_kktix_area_keyword_2_and_text.config(text=translate[language_code]["and"]) lbl_auto_guess_options.config(text=translate[language_code]["auto_guess_options"]) lbl_user_guess_string.config(text=translate[language_code]["user_guess_string"]) - + lbl_date_auto_select.config(text=translate[language_code]["date_auto_select"]) lbl_date_auto_select_mode.config(text=translate[language_code]["date_select_order"]) lbl_date_keyword.config(text=translate[language_code]["date_keyword"]) @@ -1028,7 +1098,8 @@ def applyNewLanguage(): tabControl.tab(0, text=translate[language_code]["preference"]) tabControl.tab(1, text=translate[language_code]["advanced"]) tabControl.tab(2, text=translate[language_code]["autofill"]) - tabControl.tab(3, text=translate[language_code]["about"]) + tabControl.tab(3, text=translate[language_code]["runtime"]) + tabControl.tab(4, text=translate[language_code]["about"]) global lbl_tixcraft_sid global lbl_ibon_ibonqware @@ -1385,11 +1456,11 @@ def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): ,"https://ticketplus.com.tw/ (遠大)" ,"http://www.urbtix.hk/ (城市)" ,"https://www.cityline.com/ (買飛)" - ,"https://premier.hkticketing.com/ (快達票)" ,"https://ticketing.galaxymacau.com/ (澳門銀河)" ] # 目前機器人已失效, 因為官方的 reCaptcha 可以檢測出機器人。 ''' + ,"https://premier.hkticketing.com/ (快達票)" ''' combo_homepage.set(homepage) combo_homepage.bind("<>", callbackHomepageOnChange) @@ -2222,6 +2293,85 @@ def AutofillTab(root, config_dict, language_code, UI_PADDING_X): frame_group_header.grid(column=0, row=row_count, padx=UI_PADDING_X) +def settings_timer(language_code): + while True: + update_maxbot_runtime_status(language_code) + time.sleep(0.5) + +def update_maxbot_runtime_status(language_code): + is_paused = False + if os.path.exists(CONST_MAXBOT_INT28_FILE): + is_paused = True + + try: + global lbl_maxbot_status_data + maxbot_status = translate[language_code]['status_enabled'] + if is_paused: + maxbot_status = translate[language_code]['status_paused'] + + lbl_maxbot_status_data.config(text=maxbot_status) + + global btn_idle + global btn_resume + + if not is_paused: + btn_idle.grid(column=1, row=0) + btn_resume.grid_forget() + else: + btn_resume.grid(column=2, row=0) + btn_idle.grid_forget() + + global lbl_maxbot_last_url_data + last_url = read_last_url_from_file() + lbl_maxbot_last_url_data.config(text=last_url) + except Exception as exc: + pass + + + +def RuntimeTab(root, config_dict, language_code, UI_PADDING_X): + row_count = 0 + + frame_group_header = Frame(root) + group_row_count = 0 + + maxbot_status = "" + global lbl_maxbot_status + lbl_maxbot_status = Label(frame_group_header, text=translate[language_code]['running_status']) + lbl_maxbot_status.grid(column=0, row=group_row_count, sticky = E) + + + frame_maxbot_interrupt = Frame(frame_group_header) + + global lbl_maxbot_status_data + lbl_maxbot_status_data = Label(frame_maxbot_interrupt, text=maxbot_status) + lbl_maxbot_status_data.grid(column=0, row=group_row_count, sticky = W) + + global btn_idle + global btn_resume + + btn_idle = ttk.Button(frame_maxbot_interrupt, text=translate[language_code]['idle'], command= lambda: btn_idle_clicked(language_code) ) + btn_idle.grid(column=1, row=0) + + btn_resume = ttk.Button(frame_maxbot_interrupt, text=translate[language_code]['resume'], command= lambda: btn_resume_clicked(language_code)) + btn_resume.grid(column=2, row=0) + + frame_maxbot_interrupt.grid(column=1, row=group_row_count, sticky = W) + + group_row_count +=1 + + global lbl_maxbot_last_url + lbl_maxbot_last_url = Label(frame_group_header, text=translate[language_code]['running_url']) + lbl_maxbot_last_url.grid(column=0, row=group_row_count, sticky = E) + + last_url = "" + global lbl_maxbot_last_url_data + lbl_maxbot_last_url_data = Label(frame_group_header, text=last_url) + lbl_maxbot_last_url_data.grid(column=1, row=group_row_count, sticky = W) + + frame_group_header.grid(column=0, row=row_count, padx=UI_PADDING_X) + update_maxbot_runtime_status(language_code) + def AboutTab(root, language_code): row_count = 0 @@ -2336,8 +2486,6 @@ def get_action_bar(root, language_code): btn_restore_defaults = ttk.Button(frame_action, text=translate[language_code]['restore_defaults'], command= lambda: btn_restore_defaults_clicked(language_code)) btn_restore_defaults.grid(column=3, row=0) - - return frame_action def clearFrame(frame): @@ -2367,7 +2515,11 @@ def load_GUI(root, config_dict): tabControl.add(tab3, text=translate[language_code]['autofill']) tab4 = Frame(tabControl) - tabControl.add(tab4, text=translate[language_code]['about']) + tabControl.add(tab4, text=translate[language_code]['runtime']) + + tab5 = Frame(tabControl) + tabControl.add(tab5, text=translate[language_code]['about']) + tabControl.grid(column=0, row=row_count) tabControl.select(tab1) @@ -2380,7 +2532,9 @@ def load_GUI(root, config_dict): PreferenctTab(tab1, config_dict, language_code, UI_PADDING_X) AdvancedTab(tab2, config_dict, language_code, UI_PADDING_X) AutofillTab(tab3, config_dict, language_code, UI_PADDING_X) - AboutTab(tab4, language_code) + RuntimeTab(tab4, config_dict, language_code, UI_PADDING_X) + AboutTab(tab5, language_code) + threading.Thread(target=settings_timer, args=(language_code,), daemon=True).start() def main(): @@ -2402,6 +2556,7 @@ def main(): load_GUI(root, config_dict) + GUI_SIZE_WIDTH = 510 GUI_SIZE_HEIGHT = 619