From 6d66296658c43ad703c55763e817a700c9ef6c23 Mon Sep 17 00:00:00 2001 From: CHUN YU YAO Date: Wed, 16 Nov 2022 23:43:53 +0800 Subject: [PATCH] 2022-11-16, add play sound when captcha. --- chrome_tixcraft.py | 635 +++++++++++++++++++++++++++------------------ ding-dong.mp3 | Bin 0 -> 22056 bytes ding.mp3 | Bin 0 -> 35016 bytes icon_play_3.gif | Bin 0 -> 929 bytes pip-reg.txt | 3 +- settings.json | 2 +- settings.py | 349 +++++++++++++++++-------- 7 files changed, 621 insertions(+), 368 deletions(-) create mode 100644 ding-dong.mp3 create mode 100644 ding.mp3 create mode 100644 icon_play_3.gif diff --git a/chrome_tixcraft.py b/chrome_tixcraft.py index afeda24..b43d726 100644 --- a/chrome_tixcraft.py +++ b/chrome_tixcraft.py @@ -10,17 +10,7 @@ import random # 'seleniumwire' and 'selenium 4' raise error when running python 2.x # PS: python 2.x will be removed in future. -driver_type = 'selenium' -#driver_type = 'stealth' -driver_type = 'undetected_chromedriver' - -if driver_type=="undetected_chromedriver": - # TODO: fix image re-download issue. - #from seleniumwire import webdriver - - from selenium import webdriver -else: - from selenium import webdriver +from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import Select @@ -67,7 +57,7 @@ ssl._create_default_https_context = ssl._create_unverified_context #附註1:沒有寫的很好,很多地方應該可以模組化。 #附註2: -CONST_APP_VERSION = u"MaxBot (2022.11.14) ver.5" +CONST_APP_VERSION = u"MaxBot (2022.11.16)" CONST_FROM_TOP_TO_BOTTOM = u"from top to bottom" CONST_FROM_BOTTOM_TO_TOP = u"from bottom to top" @@ -85,7 +75,6 @@ driver = None homepage = None browser = None ticket_number = None -facebook_account = None auto_press_next_step_button = False auto_fill_ticket_number = False @@ -179,7 +168,6 @@ def get_chromedriver_path(webdriver_path): return chromedriver_path def load_chromdriver_normal(webdriver_path, driver_type): - from selenium_stealth import stealth chrome_options = webdriver.ChromeOptions() chromedriver_path = get_chromedriver_path(webdriver_path) @@ -221,7 +209,6 @@ def load_chromdriver_normal(webdriver_path, driver_type): if driver_type=="stealth": from selenium_stealth import stealth - # Selenium Stealth settings stealth(driver, languages=["zh-TW", "zh"], @@ -307,14 +294,13 @@ def close_browser_tabs(driver): except Exception as excSwithFail: pass -def load_config_from_local(driver): - config_dict = get_config_dict() +def get_driver_by_config(config_dict, driver_type): + global driver global homepage global browser global debugMode global ticket_number - global facebook_account global auto_press_next_step_button global auto_fill_ticket_number global kktix_area_auto_select_mode @@ -362,18 +348,13 @@ def load_config_from_local(driver): if 'ticket_number' in config_dict: ticket_number = str(config_dict["ticket_number"]) - facebook_account = "" - if 'facebook_account' in config_dict: - facebook_account = str(config_dict["facebook_account"]) - # for ["kktix"] if 'kktix' in config_dict: auto_press_next_step_button = config_dict["kktix"]["auto_press_next_step_button"] auto_fill_ticket_number = config_dict["kktix"]["auto_fill_ticket_number"] if 'area_mode' in config_dict["kktix"]: - kktix_area_auto_select_mode = config_dict["kktix"]["area_mode"] - kktix_area_auto_select_mode = kktix_area_auto_select_mode.strip() + kktix_area_auto_select_mode = config_dict["kktix"]["area_mode"].strip() if not kktix_area_auto_select_mode in CONST_SELECT_OPTIONS_ARRAY: kktix_area_auto_select_mode = CONST_SELECT_ORDER_DEFAULT @@ -462,7 +443,6 @@ def load_config_from_local(driver): print("homepage", homepage) print("browser", browser) print("ticket_number", ticket_number) - print("facebook_account", facebook_account) # for kktix print("==[kktix]==") @@ -498,7 +478,7 @@ def load_config_from_local(driver): if homepage is None: homepage = "" if len(homepage) == 0: - homepage = "https://tixcraft.com/activity/" + homepage = "https://tixcraft.com" Root_Dir = get_app_root() webdriver_path = os.path.join(Root_Dir, "webdriver") @@ -592,6 +572,8 @@ def load_config_from_local(driver): else: try: print("goto url:", homepage) + if homepage=="https://tixcraft.com": + homepage="https://tixcraft.com/user/changeLanguage/lang/zh_tw" driver.get(homepage) except WebDriverException as exce2: print('oh no not again, WebDriverException') @@ -1034,7 +1016,13 @@ def tixcraft_redirect(driver, url): return ret -def date_auto_select(driver, url, date_auto_select_mode, date_keyword, pass_date_is_sold_out_enable, auto_reload_coming_soon_page_enable): +def tixcraft_date_auto_select(driver, url, config_dict): + # read config. + date_auto_select_mode = config_dict["tixcraft"]["date_auto_select"]["mode"] + date_keyword = config_dict["tixcraft"]["date_auto_select"]["date_keyword"] + pass_date_is_sold_out_enable = config_dict["tixcraft"]["pass_date_is_sold_out"] + auto_reload_coming_soon_page_enable = config_dict["tixcraft"]["auto_reload_coming_soon_page"] + is_date_selected = False debug_date_select = True # debug. @@ -1303,7 +1291,15 @@ def get_tixcraft_target_area(el, area_keyword, area_auto_select_mode, pass_1_sea # PS: auto refresh condition 1: no keyword + no hyperlink. # PS: auto refresh condition 2: with keyword + no hyperlink. -def area_auto_select(driver, url, area_keyword_1, area_keyword_2, area_keyword_3, area_keyword_4, area_auto_select_mode, pass_1_seat_remaining_enable): +def tixcraft_area_auto_select(driver, url, config_dict): + # read config. + 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() + area_keyword_4 = config_dict["tixcraft"]["area_auto_select"]["area_keyword_4"].strip() + area_auto_select_mode = config_dict["tixcraft"]["area_auto_select"]["mode"] + pass_1_seat_remaining_enable = config_dict["tixcraft"]["pass_1_seat_remaining"] + debugMode = True debugMode = False # for online @@ -1436,96 +1432,89 @@ def area_auto_select(driver, url, area_keyword_1, area_keyword_2, area_keyword_3 print("find submitSeat fail") ''' -def ticket_number_auto_fill(url, form_select): +def tixcraft_ticket_agree(driver): + click_plan = "B" + #click_plan = "B" + # check agree form_checkbox = None - try: - form_checkbox = driver.find_element(By.ID, 'TicketForm_agree') + if click_plan == "A": + try: + form_checkbox = driver.find_element(By.ID, 'TicketForm_agree') + except Exception as exc: + print("find TicketForm_agree fail") - if form_checkbox is not None: - try: + is_finish_checkbox_click = False + if form_checkbox is not None: + try: + # TODO: check the status: checked. + if form_checkbox.is_enabled(): form_checkbox.click() - except Exception as exc: - print("click TicketForm_agree fail") - pass - except Exception as exc: - print("find TicketForm_agree fail") + is_finish_checkbox_click = True + except Exception as exc: + print("click TicketForm_agree fail") + pass - # 使用 plan B. - try: - #driver.execute_script("$(\"input[type='checkbox']\").prop('checked', true);") - driver.execute_script("document.getElementById(\"TicketForm_agree\").checked;") - except Exception as exc: - print("javascript check TicketForm_agree fail") - print(exc) - pass + if not is_finish_checkbox_click: + # 使用 plan B. + try: + driver.execute_script("$(\"input[type='checkbox']\").prop('checked', true);") + #driver.execute_script("document.getElementById(\"TicketForm_agree\").checked;") + is_finish_checkbox_click = True + except Exception as exc: + print("javascript check TicketForm_agree fail") + print(exc) + pass - # select options - select = None - try: - #select = driver.find_element(By.TAG_NAME, 'select') - select = Select(form_select) - #select = driver.find_element(By.CSS_SELECTOR, '.mobile-select') - except Exception as exc: - print("select fail") + return is_finish_checkbox_click - if select is not None: +def tixcraft_ticket_number_auto_fill(driver, select_obj, ticket_number): + is_assign_ticket_number = False + if select_obj is not None: try: # target ticket number - select.select_by_visible_text(ticket_number) + select_obj.select_by_visible_text(ticket_number) #select.select_by_value(ticket_number) #select.select_by_index(int(ticket_number)) + is_assign_ticket_number = True except Exception as exc: print("select_by_visible_text ticket_number fail") print(exc) try: # target ticket number - select.select_by_visible_text(ticket_number) + select_obj.select_by_visible_text(ticket_number) #select.select_by_value(ticket_number) #select.select_by_index(int(ticket_number)) + is_assign_ticket_number = True except Exception as exc: print("select_by_visible_text ticket_number fail...2") print(exc) # try buy one ticket try: - select.select_by_visible_text("1") + select_obj.select_by_visible_text("1") #select.select_by_value("1") #select.select_by_index(int(ticket_number)) + is_assign_ticket_number = True except Exception as exc: print("select_by_visible_text 1 fail") pass # because click cause click wrong row. - if select is not None: - try: - # target ticket number - #select.select_by_visible_text(ticket_number) - print("assign ticker number by jQuery:",ticket_number) - driver.execute_script("$(\"input[type='select']\").val(\""+ ticket_number +"\");") - except Exception as exc: - print("jQuery select_by_visible_text ticket_number fail (after click.)") - print(exc) - - # click again. - try: - form_select.click() - except Exception as exc: - print("click select fail") - pass - - form_verifyCode = None - try: - form_verifyCode = driver.find_element(By.ID, 'TicketForm_verifyCode') - if form_verifyCode is not None: + if not is_assign_ticket_number: + if select is not None: try: - form_verifyCode.click() + # target ticket number + #select.select_by_visible_text(ticket_number) + print("assign ticker number by jQuery:",ticket_number) + driver.execute_script("$(\"input[type='select']\").val(\""+ ticket_number +"\");") + is_assign_ticket_number = True except Exception as exc: - print("click form_verifyCode fail") - pass - except Exception as exc: - print("find form_verifyCode fail") + print("jQuery select_by_visible_text ticket_number fail (after click.)") + print(exc) + + return is_assign_ticket_number def tixcraft_verify(driver, url): ret = False @@ -1666,44 +1655,99 @@ def tixcraft_verify(driver, url): return ret -def tixcraft_ticket_main(driver, url, is_verifyCode_editing): +def tixcraft_ticket_main(driver, config_dict, is_finish_checkbox_click): + if not is_finish_checkbox_click: + is_finish_checkbox_click = tixcraft_ticket_agree(driver) + + # allow agree not enable to assign ticket number. form_select = None try: #form_select = driver.find_element(By.TAG_NAME, 'select') + #PS: select box may appear many in the page with different price. form_select = driver.find_element(By.CSS_SELECTOR, '.mobile-select') except Exception as exc: print("find select fail") pass + select_obj = None if form_select is not None: + is_visible = False try: - #print("get select ticket value:" + Select(form_select).first_selected_option.text) - if Select(form_select).first_selected_option.text=="0": - is_verifyCode_editing = False + is_visible = form_select.is_enabled() except Exception as exc: - print("query selected option fail") - print(exc) pass - - if is_verifyCode_editing == False: - ticket_number_auto_fill(url, form_select) - - # start to input verify code. + if is_visible: try: - #driver.execute_script("$('#TicketForm_verifyCode').focus();") - driver.execute_script("document.getElementById(\"TicketForm_verifyCode\").focus();") - - is_verifyCode_editing = True - print("goto is_verifyCode_editing== True") + select_obj = Select(form_select) except Exception as exc: - print(exc) pass - else: - #print("is_verifyCode_editing") - # do nothing here. + + is_verifyCode_editing = False + is_assign_ticket_number = False + if not select_obj is None: + row_text = None + try: + row_text = select_obj.first_selected_option.text + except Exception as exc: pass + if not row_text is None: + if len(row_text) > 0: + if row_text != "0": + # ticket assign. + is_assign_ticket_number = True - return is_verifyCode_editing + # must wait select object ready to assign ticket number. + if not is_assign_ticket_number: + ticket_number = str(config_dict["ticket_number"]) + is_assign_ticket_number = tixcraft_ticket_number_auto_fill(driver, select_obj, ticket_number) + + # must wait ticket number assign to focus captcha. + if is_assign_ticket_number: + # only this case:"ticket number change by bot" to play sound! + play_captcha_sound = config_dict["advanced"]["play_captcha_sound"]["enable"] + captcha_sound_filename = config_dict["advanced"]["play_captcha_sound"]["filename"].strip() + if play_captcha_sound: + app_root = get_app_root() + captcha_sound_filename = os.path.join(app_root, captcha_sound_filename) + play_mp3(captcha_sound_filename) + + # only this case to focus() + # start to input verify code. + form_verifyCode = None + try: + form_verifyCode = driver.find_element(By.ID, 'TicketForm_verifyCode') + except Exception as exc: + print("find form_verifyCode fail") + + if form_verifyCode is not None: + is_visible = False + try: + if form_verifyCode.is_enabled(): + is_visible = True + except Exception as exc: + pass + + if is_visible: + try: + form_verifyCode.click() + is_verifyCode_editing = True + except Exception as exc: + print("click form_verifyCode fail, tring to use javascript.") + # plan B + try: + driver.execute_script("document.getElementById(\"TicketForm_verifyCode\").focus();") + is_verifyCode_editing = True + except Exception as exc: + print("click form_verifyCode fail") + pass + pass + + print("is_finish_checkbox_click:", is_finish_checkbox_click) + + if is_verifyCode_editing: + print("goto is_verifyCode_editing == True") + + return is_verifyCode_editing, is_finish_checkbox_click # PS: There are two "Next" button in kktix. # : 1: /events/xxx @@ -1713,7 +1757,7 @@ def kktix_events_press_next_button(driver): ret = False # let javascript to enable button. - time.sleep(0.2) + time.sleep(0.1) wait = WebDriverWait(driver, 1) next_step_button = None @@ -1787,8 +1831,6 @@ def kktix_press_next_button(driver): ret = True except Exception as exc: pass - - return ret def kktix_captcha_text_value(captcha_inner_div): @@ -1979,7 +2021,7 @@ def kktix_travel_price_list(driver, kktix_area_keyword, kktix_date_keyword): return is_ticket_number_assigened, areas -def kktix_assign_ticket_number(driver, ticket_number, kktix_area_keyword, kktix_date_keyword): +def kktix_assign_ticket_number(driver, ticket_number, kktix_area_auto_select_mode, kktix_area_keyword, kktix_date_keyword): show_debug_message = True # debug. show_debug_message = False # online @@ -2184,7 +2226,16 @@ def kktix_check_register_status(url): #print("registerStatus:", registerStatus) return registerStatus -def kktix_reg_new_main(url, answer_index, registrationsNewApp_div, is_finish_checkbox_click, auto_fill_ticket_number, ticket_number, kktix_area_keyword, kktix_date_keyword): +def kktix_reg_new_main(url, answer_index, registrationsNewApp_div, is_finish_checkbox_click, config_dict): + # read config. + auto_press_next_step_button = config_dict["kktix"]["auto_press_next_step_button"] + auto_fill_ticket_number = config_dict["kktix"]["auto_fill_ticket_number"] + ticket_number = str(config_dict["ticket_number"]) + kktix_area_keyword = config_dict["kktix"]["area_keyword"].strip() + kktix_date_keyword = config_dict["kktix"]["date_keyword"].strip() + kktix_area_auto_select_mode = config_dict["kktix"]["area_mode"] + auto_guess_options = config_dict["kktix"]["auto_guess_options"] + show_debug_message = True # debug. show_debug_message = False # online @@ -2194,7 +2245,7 @@ def kktix_reg_new_main(url, answer_index, registrationsNewApp_div, is_finish_che is_assign_ticket_number = False if auto_fill_ticket_number: for retry_index in range(8): - is_assign_ticket_number = kktix_assign_ticket_number(driver, ticket_number, kktix_area_keyword, kktix_date_keyword) + is_assign_ticket_number = kktix_assign_ticket_number(driver, ticket_number, kktix_area_auto_select_mode, kktix_area_keyword, kktix_date_keyword) if is_assign_ticket_number: break #print('is_assign_ticket_number:', is_assign_ticket_number) @@ -2693,7 +2744,7 @@ def kktix_reg_new_main(url, answer_index, registrationsNewApp_div, is_finish_che return answer_index -def kktix_reg_new(driver, url, answer_index, kktix_register_status_last): +def kktix_reg_new(driver, url, answer_index, kktix_register_status_last, config_dict): registerStatus = kktix_register_status_last #--------------------------- @@ -2756,21 +2807,15 @@ def kktix_reg_new(driver, url, answer_index, kktix_register_status_last): answer_index = -1 registerStatus = None else: - global auto_fill_ticket_number - global ticket_number - global kktix_area_keyword - global kktix_date_keyword - answer_index = kktix_reg_new_main(url, answer_index, registrationsNewApp_div, is_finish_checkbox_click, auto_fill_ticket_number, ticket_number, kktix_area_keyword, kktix_date_keyword) - + answer_index = kktix_reg_new_main(url, answer_index, registrationsNewApp_div, is_finish_checkbox_click, config_dict) return answer_index, registerStatus - # PURPOSE: get target area list. # PS: this is main block, use keyword to get rows. # PS: it seems use date_auto_select_mode instead of area_auto_select_mode -def get_fami_target_area(date_keyword, area_keyword_1, area_keyword_2, area_keyword_3, area_keyword_4, area_auto_select_mode): +def get_fami_target_area(driver, date_keyword, area_keyword_1, area_keyword_2, area_keyword_3, area_keyword_4, area_auto_select_mode): show_debug_message = True # debug. #show_debug_message = False # online @@ -2931,37 +2976,41 @@ def get_fami_target_area(date_keyword, area_keyword_1, area_keyword_2, area_keyw return areas -def fami_activity(driver, url): +def fami_activity(driver): #print("fami_activity bingo") #--------------------------- # part 1: press "buy" button. #--------------------------- fami_start_to_buy_button = None - fami_start_to_buy_button = driver.find_element(By.ID, 'buyWaiting') + try: + fami_start_to_buy_button = driver.find_element(By.ID, 'buyWaiting') + except Exception as exc: + pass + + is_visible = False if fami_start_to_buy_button is not None: - is_visible = False try: if fami_start_to_buy_button.is_enabled(): is_visible = True except Exception as exc: pass - if fami_start_to_buy_button.is_enabled(): + if is_visible: + try: + fami_start_to_buy_button.click() + except Exception as exc: + print("click buyWaiting button fail...") + #print(exc) + #pass try: - fami_start_to_buy_button.click() + driver.execute_script("arguments[0].click();", fami_start_to_buy_button) except Exception as exc: - print("click buyWaiting button fail...") - #print(exc) - #pass - try: - driver.execute_script("arguments[0].click();", fami_start_to_buy_button) - except Exception as exc: - pass + pass -def fami_home(driver, url): - print("fami_home bingo") +def fami_home(driver, url, config_dict): + #print("fami_home bingo") global is_assign_ticket_number global ticket_number @@ -2974,58 +3023,73 @@ def fami_home(driver, url): global area_auto_select_mode - is_select_box_visible = False #--------------------------- # part 3: fill ticket number. #--------------------------- ticket_el = None - is_assign_ticket_number = False try: my_css_selector = "tr.ticket > td > select" ticket_el = driver.find_element(By.CSS_SELECTOR, my_css_selector) - if ticket_el is not None: - if ticket_el.is_enabled(): - is_select_box_visible = True - - ticket_number_select = Select(ticket_el) - if ticket_number_select is not None: - try: - #print("get select ticket value:" + Select(ticket_number_select).first_selected_option.text) - if ticket_number_select.first_selected_option.text=="0" or ticket_number_select.first_selected_option.text=="選擇張數": - # target ticket number - ticket_number_select.select_by_visible_text(ticket_number) - is_assign_ticket_number = True - except Exception as exc: - print("select_by_visible_text ticket_number fail") - print(exc) - - try: - # try target ticket number twice - ticket_number_select.select_by_visible_text(ticket_number) - is_assign_ticket_number = True - except Exception as exc: - print("select_by_visible_text ticket_number fail...2") - print(exc) - - # try buy one ticket - try: - ticket_number_select.select_by_visible_text("1") - is_assign_ticket_number = True - except Exception as exc: - print("select_by_visible_text 1 fail") - pass except Exception as exc: pass print("click buyWaiting button fail") #print(exc) + is_select_box_visible = False + if ticket_el is not None: + try: + if ticket_el.is_enabled(): + is_select_box_visible = True + except Exception as exc: + pass + + is_assign_ticket_number = False + if is_select_box_visible: + ticket_number_select = None + try: + ticket_number_select = Select(ticket_el) + except Exception as exc: + pass + + if ticket_number_select is not None: + try: + #print("get select ticket value:" + Select(ticket_number_select).first_selected_option.text) + if ticket_number_select.first_selected_option.text=="0" or ticket_number_select.first_selected_option.text=="選擇張數": + # target ticket number + ticket_number_select.select_by_visible_text(ticket_number) + is_assign_ticket_number = True + except Exception as exc: + print("select_by_visible_text ticket_number fail") + print(exc) + + try: + # try target ticket number twice + ticket_number_select.select_by_visible_text(ticket_number) + is_assign_ticket_number = True + except Exception as exc: + print("select_by_visible_text ticket_number fail...2") + print(exc) + + # try buy one ticket + try: + ticket_number_select.select_by_visible_text("1") + is_assign_ticket_number = True + except Exception as exc: + print("select_by_visible_text 1 fail") + pass + #--------------------------- # part 4: press "next" button. #--------------------------- if is_assign_ticket_number: - my_css_selector = "div.col > a.btn" - fami_assign_site_button = driver.find_element(By.CSS_SELECTOR, my_css_selector) + fami_assign_site_button = None + try: + my_css_selector = "div.col > a.btn" + fami_assign_site_button = driver.find_element(By.CSS_SELECTOR, my_css_selector) + except Exception as exc: + pass + if fami_assign_site_button is not None: is_visible = False try: @@ -3050,7 +3114,7 @@ def fami_home(driver, url): #--------------------------- # part 2: select keywords #--------------------------- - areas = get_fami_target_area(date_keyword, area_keyword_1, area_keyword_2, area_keyword_3, area_keyword_4, area_auto_select_mode) + areas = get_fami_target_area(driver, date_keyword, area_keyword_1, area_keyword_2, area_keyword_3, area_keyword_4, area_auto_select_mode) area_target = None if areas is not None: @@ -3130,7 +3194,7 @@ def urbtix_ticket_number_auto_select(driver, url, ticket_number): except Exception as exc: print("find ticket_number select fail...") - time.sleep(0.1) + #time.sleep(0.1) #print(exc) pass @@ -3248,7 +3312,7 @@ def urbtix_next_button_press(driver, url): return ret -def urbtix_performance(driver, url): +def urbtix_performance(driver, url, config_dict): #print("urbtix performance bingo") global auto_fill_ticket_number @@ -3494,7 +3558,7 @@ def cityline_next_button_press(url): return ret -def cityline_event(driver, url): +def cityline_event(driver): ret = False is_non_member_displayed = False @@ -3569,7 +3633,7 @@ def cityline_captcha_auto_focus(url): return ret -def cityline_performance(driver, url): +def cityline_performance(driver, url, config_dict): #print("cityline bingo") if "performance.do;" in url: cityline_captcha_auto_focus(url) @@ -3599,35 +3663,142 @@ def cityline_performance(driver, url): if click_ret: break - -def facebook_login(driver, url): +def facebook_login(driver, facebook_account): ret = False + el_email = None try: - el = driver.find_element(By.CSS_SELECTOR, '#email') - if el is not None: - ret = True - if el.is_enabled(): - inputed_text = el.get_attribute('value') - if len(inputed_text) == 0: - el.send_keys(facebook_account) - ret = True + el_email = driver.find_element(By.CSS_SELECTOR, '#email') except Exception as exc: #print("find #email fail") #print(exc) pass + is_visible = False + if el_email is not None: + try: + if el_email.is_enabled(): + if el_email.is_displayed(): + is_visible = True + except Exception as exc: + #print("find #email fail") + #print(exc) + pass + + is_email_sent = False + if is_visible: + try: + inputed_text = el_email.get_attribute('value') + if inputed_text is not None: + if len(inputed_text) == 0: + el_email.send_keys(facebook_account) + is_email_sent = True + except Exception as exc: + #print("find #email fail") + #print(exc) + pass + + el_pass = None + if is_email_sent: + try: + el_pass = driver.find_element(By.CSS_SELECTOR, '#pass') + except Exception as exc: + #print("find #email fail") + #print(exc) + pass + + is_visible = False + if el_pass is not None: + try: + if el_pass.is_enabled(): + if el_pass.is_displayed(): + is_visible = True + except Exception as exc: + #print("find #email fail") + #print(exc) + pass + + if is_visible: + try: + el_pass.click() + except Exception as exc: + #print("find #email fail") + #print(exc) + pass + return ret +def play_mp3(sound_filename): + import threading + from playsound import playsound + try: + threading.Thread(target=playsound, args=(sound_filename,), daemon=True).start() + #playsound(sound_filename) + except Exception as exc: + msg=str(exc) + print("play sound exeption:", msg) + +# purpose: check alert poped. +# PS: current version not enable... +def check_pop_alert(driver): + is_alert_popup = False + + # https://stackoverflow.com/questions/57481723/is-there-a-change-in-the-handling-of-unhandled-alert-in-chromedriver-and-chrome + default_close_alert_text = [] + if len(default_close_alert_text) > 0: + try: + alert = None + if not driver is None: + alert = driver.switch_to.alert + if not alert is None: + if not alert.text is None: + is_match_auto_close_text = False + for txt in default_close_alert_text: + if len(txt) > 0: + if txt in alert.text: + is_match_auto_close_text = True + #print("alert3 text:", alert.text) + + if is_match_auto_close_text: + alert.accept() + print("alert3 accepted") + + is_alert_popup = True + else: + print("alert3 not detected") + except NoAlertPresentException as exc1: + #logger.error('NoAlertPresentException for alert') + pass + except NoSuchWindowException: + #print('NoSuchWindowException2 at this url:', url ) + #print("last_url:", last_url) + try: + window_handles_count = len(driver.window_handles) + if window_handles_count >= 1: + driver.switch_to.window(driver.window_handles[0]) + except Exception as excSwithFail: + pass + except Exception as exc: + logger.error('Exception2 for alert') + logger.error(exc, exc_info=True) + + return is_alert_popup def main(): - global driver - driver = load_config_from_local(driver) + config_dict = get_config_dict() + + driver_type = 'selenium' + #driver_type = 'stealth' + driver_type = 'undetected_chromedriver' + + driver = get_driver_by_config(config_dict, driver_type) # internal variable. 說明:這是一個內部變數,請略過。 url = "" last_url = "" + # for tixcraft is_verifyCode_editing = False + is_finish_checkbox_click = False # for kktix answer_index = -1 @@ -3647,44 +3818,7 @@ def main(): print("web driver not accessible!") break - # https://stackoverflow.com/questions/57481723/is-there-a-change-in-the-handling-of-unhandled-alert-in-chromedriver-and-chrome - default_close_alert_text = [] - if len(default_close_alert_text) > 0: - try: - alert = None - if not driver is None: - alert = driver.switch_to.alert - if not alert is None: - if not alert.text is None: - is_match_auto_close_text = False - for txt in default_close_alert_text: - if len(txt) > 0: - if txt in alert.text: - is_match_auto_close_text = True - #print("alert3 text:", alert.text) - - if is_match_auto_close_text: - alert.accept() - print("alert3 accepted") - - is_alert_popup = True - else: - print("alert3 not detected") - except NoAlertPresentException as exc1: - #logger.error('NoAlertPresentException for alert') - pass - except NoSuchWindowException: - #print('NoSuchWindowException2 at this url:', url ) - #print("last_url:", last_url) - try: - window_handles_count = len(driver.window_handles) - if window_handles_count >= 1: - driver.switch_to.window(driver.window_handles[0]) - except Exception as excSwithFail: - pass - except Exception as exc: - logger.error('Exception2 for alert') - logger.error(exc, exc_info=True) + is_alert_popup = check_pop_alert(driver) #MUST "do nothing: if alert popup. #print("is_alert_popup:", is_alert_popup) @@ -3761,6 +3895,7 @@ def main(): sys.exit() break + # not is above case, print exception. print("Exception:", str_exc) pass @@ -3779,7 +3914,7 @@ def main(): print(url) last_url = url - # for Max's test. + # for Max's manuall test. if '/Downloads/varify.html' in url: tixcraft_verify(driver, url) @@ -3813,53 +3948,41 @@ def main(): # start to redirecting. continue - global date_auto_select_enable - global date_auto_select_mode - global date_keyword - - global pass_date_is_sold_out_enable - global auto_reload_coming_soon_page_enable - is_date_selected = False + date_auto_select_enable = config_dict["tixcraft"]["date_auto_select"]["enable"] if date_auto_select_enable: - is_date_selected = date_auto_select(driver, url, date_auto_select_mode, date_keyword, pass_date_is_sold_out_enable, auto_reload_coming_soon_page_enable) + is_date_selected = tixcraft_date_auto_select(driver, url, config_dict) if is_date_selected: # start to redirecting. continue # choose area - global area_auto_select_enable - global pass_1_seat_remaining_enable - - global area_keyword_1 - global area_keyword_2 - global area_keyword_3 - global area_keyword_4 - + area_auto_select_enable = config_dict["tixcraft"]["area_auto_select"]["enable"] if area_auto_select_enable: - area_auto_select(driver, url, area_keyword_1, area_keyword_2, area_keyword_3, area_keyword_4, area_auto_select_mode, pass_1_seat_remaining_enable) + tixcraft_area_auto_select(driver, url, config_dict) if '/ticket/verify/' in url: tixcraft_verify(driver, url) # main app, to select ticket number. if '/ticket/ticket/' in url: - is_verifyCode_editing = tixcraft_ticket_main(driver, url, is_verifyCode_editing) + if not is_verifyCode_editing: + is_verifyCode_editing, is_finish_checkbox_click = tixcraft_ticket_main(driver, config_dict, is_finish_checkbox_click) else: - # not is input verify code, reset flag. is_verifyCode_editing = False - - global auto_press_next_step_button + is_finish_checkbox_click = False # for kktix.cc and kktix.com if 'kktix.c' in url: + auto_press_next_step_button = config_dict["kktix"]["auto_press_next_step_button"] + # 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: continue if '/registrations/new' in url: - answer_index, kktix_register_status_last = kktix_reg_new(driver, url, answer_index, kktix_register_status_last) + answer_index, kktix_register_status_last = kktix_reg_new(driver, url, answer_index, kktix_register_status_last, config_dict) else: is_event_page = False if '/events/' in url: @@ -3879,10 +4002,9 @@ def main(): # for famiticket if 'famiticket.com' in url: if '/Home/Activity/Info/' in url: - fami_activity(driver, url) + fami_activity(driver) if '/Sales/Home/Index/' in url: - fami_home(driver, url) - + fami_home(driver, url, config_dict) # for urbtix # https://ticket.urbtix.hk/internet/secure/event/37348/performanceDetail @@ -3910,11 +4032,11 @@ def main(): pass if '/performanceDetail/' in url: - urbtix_performance(driver, url) + urbtix_performance(driver, url, config_dict) if 'cityline.com' in url: if '/event.do' in url: - cityline_event(driver, url) + cityline_event(driver) #pass if '/Events.do' in url: @@ -3925,8 +4047,15 @@ def main(): pass if '/performance.do' in url: - cityline_performance(driver, url) + cityline_performance(driver, url, config_dict) + # for facebook + facebook_login_url = 'https://www.facebook.com/login.php?' + + if url[:len(facebook_login_url)]==facebook_login_url: + facebook_account = config_dict["advanced"]["facebook_account"].strip() + if len(facebook_account) > 4: + facebook_login(driver, facebook_account) if __name__ == "__main__": main() diff --git a/ding-dong.mp3 b/ding-dong.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..7b216ec76798b3478a3524a5c5b1f2d65497648d GIT binary patch literal 22056 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 gXme9K8lXEkw#mzj}ChFr;TO(KMW2}CIA2c literal 0 HcmV?d00001 diff --git a/ding.mp3 b/ding.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..1d3b97f1b3d9b1e13fe634706049afd8704dca96 GIT binary patch literal 35016 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 literal 0 HcmV?d00001 diff --git a/icon_play_3.gif b/icon_play_3.gif new file mode 100644 index 0000000000000000000000000000000000000000..aa89516ac3253c03c60880619470b6b5d6b702f6 GIT binary patch literal 929 zcmZ?wbhEHb6k-r!`2OFJfq~(SvGFJx4S^9D0u28d6#sMkxrPKgI|jHK=@~FH0`r67 zPZmxV1||j_Rt9DUP6i%^Mo1|Y~E3cf3N5R8GZG8M%JsumA ej&$-Hs-20Lka(y8X3BqrDGVT;f20ZPv<3j9^IT>C literal 0 HcmV?d00001 diff --git a/pip-reg.txt b/pip-reg.txt index 643ee1e..0b2e318 100644 --- a/pip-reg.txt +++ b/pip-reg.txt @@ -4,4 +4,5 @@ cryptography idna selenium selenium-stealth -undetected-chromedriver \ No newline at end of file +undetected-chromedriver +playsound \ No newline at end of file diff --git a/settings.json b/settings.json index ca64411..7d318a7 100644 --- a/settings.json +++ b/settings.json @@ -1 +1 @@ -{"tixcraft": {"pass_1_seat_remaining": true, "area_auto_select": {"mode": "from top to bottom", "enable": true, "area_keyword_1": "", "area_keyword_2": "", "area_keyword": "", "area_keyword_3": "", "area_keyword_4": ""}, "auto_reload_coming_soon_page": true, "date_auto_select": {"enable": true, "date_keyword": "", "mode": "from top to bottom"}, "pass_date_is_sold_out": true}, "homepage1": "https://kktix.com", "homepage2": "https://tixcraft.com", "debug": false, "facebook_account": "", "browser1": "chrome", "browser2": "firefox", "kktix": {"auto_guess_options": true, "auto_fill_ticket_price": "$1,500", "answer_dictionary": "", "area_keyword": "", "auto_press_next_step_button": true, "auto_fill_ticket_number": true, "area_mode": "from top to bottom", "date_keyword": ""}, "ticket_number": 2, "homepage": "https://tixcraft.com", "browser": "chrome", "language": "\u7e41\u9ad4\u4e2d\u6587"} \ No newline at end of file +{"tixcraft": {"pass_1_seat_remaining": true, "area_auto_select": {"enable": true, "area_keyword": "", "mode": "from top to bottom", "area_keyword_4": "", "area_keyword_1": "", "area_keyword_2": "", "area_keyword_3": ""}, "auto_reload_coming_soon_page": true, "date_auto_select": {"enable": true, "date_keyword": "", "mode": "from top to bottom"}, "pass_date_is_sold_out": true}, "homepage1": "https://kktix.com", "language": "\u7e41\u9ad4\u4e2d\u6587", "homepage2": "https://tixcraft.com", "kktix": {"auto_guess_options": true, "answer_dictionary": "", "area_keyword": "", "auto_press_next_step_button": true, "area_mode": "from top to bottom", "auto_fill_ticket_number": true, "auto_fill_ticket_price": "$1,500", "date_keyword": ""}, "facebook_account": "", "browser1": "chrome", "browser2": "firefox", "debug": false, "ticket_number": 2, "homepage": "https://tixcraft.com", "browser": "chrome", "advanced": {"play_captcha_sound": {"enable": true, "filename": "ding-dong.mp3"}, "facebook_account": ""}} \ No newline at end of file diff --git a/settings.py b/settings.py index 6f9dbc4..5f96106 100644 --- a/settings.py +++ b/settings.py @@ -20,7 +20,7 @@ import platform import json import webbrowser -CONST_APP_VERSION = u"MaxBot (2022.11.14) ver.5" +CONST_APP_VERSION = u"MaxBot (2022.11.16)" CONST_FROM_TOP_TO_BOTTOM = u"from top to bottom" CONST_FROM_BOTTOM_TO_TOP = u"from bottom to top" @@ -42,6 +42,7 @@ translate={} URL_DONATE = 'https://max-everyday.com/about/#donate' URL_HELP = 'https://max-everyday.com/2018/03/tixcraft-bot/' URL_RELEASE = 'https://github.com/max32002/tixcraft_bot/releases' +URL_FB = 'https://www.facebook.com/maxbot.ticket' def load_translate(): @@ -72,15 +73,22 @@ def load_translate(): 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["preference"] = 'Preference' + en_us["advanced"] = 'Advanced' + en_us["about"] = 'About' + en_us["run"] = 'Run' en_us["save"] = 'Save' + en_us["exit"] = 'Close' + + en_us["play_captcha_sound"] = 'Play sound when captcha' + en_us["captcha_sound_filename"] = 'captcha sound filename' + en_us["facebook_account"] = 'Facebook account' + + en_us["maxbot_slogan"] = 'MaxBot is a FREE and open source bot program. Good luck getting your expected ticket.' en_us["donate"] = 'Donate' en_us["help"] = 'Help' - en_us["preference"] = 'Preference' en_us["release"] = 'Release' - en_us["exit"] = 'Close' - en_us["about"] = 'About' - en_us["maxbot_slogan"] = 'MaxBot is a FREE and open source bot program. Good luck getting your expected ticket.' zh_tw={} zh_tw["homepage"] = '售票網站' @@ -108,15 +116,22 @@ def load_translate(): zh_tw["pass_date_is_sold_out"] = '避開「搶購一空」的場次' zh_tw["auto_reload_coming_soon_page"] = '自動刷新倒數中的活動頁面' + zh_tw["preference"] = '偏好設定' + zh_tw["advanced"] = '進階設定' + zh_tw["about"] = '關於' + zh_tw["run"] = '搶票' zh_tw["save"] = '存檔' - zh_tw["donate"] = '打賞' - zh_tw["help"] = '使用教學' - zh_tw["preference"] = '偏好設定' - zh_tw["release"] = '所有可用版本' zh_tw["exit"] = '關閉' - zh_tw["about"] = '關於' + + zh_tw["play_captcha_sound"] = '輸入驗證碼時播放音效' + zh_tw["captcha_sound_filename"] = '驗證碼用音效檔' + zh_tw["facebook_account"] = 'Facebook 帳號' + zh_tw["maxbot_slogan"] = 'MaxBot是一個免費、開放原始碼的搶票機器人。\n祝你好運,買得到預期中的票。' + zh_tw["donate"] = '打賞' + zh_tw["release"] = '所有可用版本' + zh_tw["help"] = '使用教學' zh_cn={} zh_cn["homepage"] = '售票网站' @@ -143,16 +158,23 @@ def load_translate(): zh_cn["pass_1_seat_remaining"] = '避开“剩余 1”的区域' zh_cn["pass_date_is_sold_out"] = '避开“抢购一空”的场次' zh_cn["auto_reload_coming_soon_page"] = '自动刷新倒数中的活动页面' - zh_cn["maxbot_slogan"] = 'MaxBot 是一个免费的开源机器人程序。\n祝你好运,买得到预期中的票。' + + zh_cn["preference"] = '偏好设定' + zh_cn["advanced"] = '進階設定' + zh_cn["about"] = '关于' zh_cn["run"] = '抢票' zh_cn["save"] = '存档' + zh_cn["exit"] = '关闭' + + zh_cn["play_captcha_sound"] = '輸入驗證碼時播放音效' + zh_cn["captcha_sound_filename"] = '驗證碼用音效檔' + zh_cn["facebook_account"] = 'Facebook 帳號' + + zh_cn["maxbot_slogan"] = 'MaxBot 是一个免费的开源机器人程序。\n祝你好运,买得到预期中的票。' zh_cn["donate"] = '打赏' zh_cn["help"] = '使用教学' - zh_cn["preference"] = '偏好设定' zh_cn["release"] = '所有可用版本' - zh_cn["exit"] = '关闭' - zh_cn["about"] = '关于' ja_jp={} ja_jp["homepage"] = 'ホームページ' @@ -180,15 +202,22 @@ def load_translate(): ja_jp["pass_date_is_sold_out"] = '「売り切れ」公演を避ける' ja_jp["auto_reload_coming_soon_page"] = '公開予定のページをリロード' + ja_jp["preference"] = '設定' + ja_jp["advanced"] = '高度な設定' + ja_jp["about"] = '情報' + ja_jp["run"] = 'チケットを取る' ja_jp["save"] = '保存' + ja_jp["exit"] = '閉じる' + + ja_jp["play_captcha_sound"] = 'キャプチャ時に音を鳴らす' + ja_jp["captcha_sound_filename"] = 'サウンドファイル名' + ja_jp["facebook_account"] = 'Facebookのアカウント' + + ja_jp["maxbot_slogan"] = 'MaxBot は無料のオープン ソース ボット プログラムです。 頑張って予定のチケットを手に入れてください。' ja_jp["donate"] = '寄付' ja_jp["help"] = '利用方法' - ja_jp["preference"] = '設定' ja_jp["release"] = 'リリース' - ja_jp["exit"] = '閉じる' - ja_jp["about"] = '情報' - ja_jp["maxbot_slogan"] = 'MaxBot は無料のオープン ソース ボット プログラムです。 頑張って予定のチケットを手に入れてください。' translate['en_us']=en_us translate['zh_tw']=zh_tw @@ -196,7 +225,7 @@ def load_translate(): translate['ja_jp']=ja_jp return translate -def load_json(): +def get_app_root(): # 讀取檔案裡的參數值 basis = "" if hasattr(sys, 'frozen'): @@ -204,6 +233,10 @@ def load_json(): else: basis = sys.argv[0] app_root = os.path.dirname(basis) + return app_root + +def load_json(): + app_root = get_app_root() # overwrite config path. config_filepath = os.path.join(app_root, 'settings.json') @@ -231,7 +264,6 @@ def btn_save_act(slience_mode=False): global combo_browser global combo_language global combo_ticket_number - #global txt_facebook_account global chk_state_auto_press_next_step_button global chk_state_auto_fill_ticket_number @@ -254,11 +286,14 @@ def btn_save_act(slience_mode=False): global combo_area_auto_select_mode global chk_state_pass_1_seat_remaining - global chk_state_pass_date_is_sold_out - global chk_state_auto_reload_coming_soon_page + global txt_facebook_account + global chk_state_play_captcha_sound + global txt_captcha_sound_filename + + if is_all_data_correct: if combo_homepage.get().strip()=="": is_all_data_correct = False @@ -288,10 +323,9 @@ def btn_save_act(slience_mode=False): config_dict["ticket_number"] = int(combo_ticket_number.get().strip()) if is_all_data_correct: - #config_dict["facebook_account"] = txt_facebook_account.get().strip() - pass + if not 'kktix' in config_dict: + config_dict['kktix']={} - if is_all_data_correct: config_dict["kktix"]["auto_press_next_step_button"] = bool(chk_state_auto_press_next_step_button.get()) config_dict["kktix"]["auto_fill_ticket_number"] = bool(chk_state_auto_fill_ticket_number.get()) config_dict["kktix"]["area_mode"] = combo_kktix_area_mode.get().strip() @@ -301,6 +335,9 @@ def btn_save_act(slience_mode=False): #config_dict["kktix"]["answer_dictionary"] = txt_kktix_answer_dictionary.get().strip() config_dict["kktix"]["auto_guess_options"] = bool(chk_state_auto_guess_options.get()) + if not 'tixcraft' in config_dict: + config_dict['tixcraft']={} + config_dict["tixcraft"]["date_auto_select"]["enable"] = bool(chk_state_date_auto_select.get()) config_dict["tixcraft"]["date_auto_select"]["date_keyword"] = txt_date_keyword.get().strip() @@ -317,6 +354,17 @@ def btn_save_act(slience_mode=False): config_dict["tixcraft"]["pass_date_is_sold_out"] = bool(chk_state_pass_date_is_sold_out.get()) config_dict["tixcraft"]["auto_reload_coming_soon_page"] = bool(chk_state_auto_reload_coming_soon_page.get()) + if not 'advanced' in config_dict: + config_dict['advanced']={} + + if not 'play_captcha_sound' in config_dict['advanced']: + config_dict['advanced']['play_captcha_sound']={} + + config_dict["advanced"]["play_captcha_sound"]["enable"] = bool(chk_state_play_captcha_sound.get()) + config_dict["advanced"]["play_captcha_sound"]["filename"] = txt_captcha_sound_filename.get().strip() + + config_dict["advanced"]["facebook_account"] = txt_facebook_account.get().strip() + # save config. if is_all_data_correct: import json @@ -376,9 +424,28 @@ def btn_run_clicked(): s=subprocess.Popen([interpreter_binary_alt, 'chrome_tixcraft.py'], cwd=working_dir) except Exception as exc: msg=str(exc) - messagebox.showinfo(title="Debug2", message=msg) + print("exeption:", msg) + #messagebox.showinfo(title="Debug2", message=msg) pass +def btn_preview_sound_clicked(): + global txt_captcha_sound_filename + new_sound_filename = txt_captcha_sound_filename.get().strip() + #print("new_sound_filename:", new_sound_filename) + app_root = get_app_root() + new_sound_filename = os.path.join(app_root, new_sound_filename) + play_mp3(new_sound_filename) + +def play_mp3(sound_filename): + import threading + from playsound import playsound + try: + threading.Thread(target=playsound, args=(sound_filename,), daemon=True).start() + #playsound(sound_filename) + except Exception as exc: + msg=str(exc) + print("play sound exeption:", msg) + def open_url(url): webbrowser.open_new(url) @@ -449,6 +516,7 @@ def applyNewLanguage(): global chk_pass_1_seat_remaining global chk_pass_date_is_sold_out global chk_auto_reload_coming_soon_page + global chk_play_captcha_sound global tabControl @@ -489,9 +557,18 @@ def applyNewLanguage(): chk_pass_1_seat_remaining.config(text=translate[language_code]["enable"]) chk_pass_date_is_sold_out.config(text=translate[language_code]["enable"]) chk_auto_reload_coming_soon_page.config(text=translate[language_code]["enable"]) + chk_play_captcha_sound.config(text=translate[language_code]["enable"]) tabControl.tab(0, text=translate[language_code]["preference"]) - tabControl.tab(1, text=translate[language_code]["about"]) + tabControl.tab(1, text=translate[language_code]["advanced"]) + tabControl.tab(2, text=translate[language_code]["about"]) + + global lbl_facebook_account + global lbl_play_captcha_sound + global lbl_captcha_sound_filename + lbl_facebook_account.config(text=translate[language_code]["facebook_account"]) + lbl_play_captcha_sound.config(text=translate[language_code]["play_captcha_sound"]) + lbl_captcha_sound_filename.config(text=translate[language_code]["captcha_sound_filename"]) lbl_slogan.config(text=translate[language_code]["maxbot_slogan"]) lbl_help.config(text=translate[language_code]["help"]) @@ -663,21 +740,6 @@ def showHideTixcraftBlocks(): def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): - global lbl_homepage - global lbl_browser - global lbl_language - global lbl_ticket_number - - global lbl_kktix - global lbl_tixcraft - - lbl_homepage = None - lbl_browser = None - lbl_language = None - lbl_ticket_number = None - lbl_kktix = None - lbl_tixcraft = None - homepage = None browser = None language = "English" @@ -729,32 +791,21 @@ def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): if u'ticket_number' in config_dict: ticket_number = str(config_dict["ticket_number"]) - facebook_account = "" - if 'facebook_account' in config_dict: - facebook_account = str(config_dict["facebook_account"]) - # for ["kktix"] if 'kktix' in config_dict: auto_press_next_step_button = config_dict["kktix"]["auto_press_next_step_button"] auto_fill_ticket_number = config_dict["kktix"]["auto_fill_ticket_number"] if 'area_mode' in config_dict["kktix"]: - kktix_area_mode = config_dict["kktix"]["area_mode"] - kktix_area_mode = kktix_area_mode.strip() + kktix_area_mode = config_dict["kktix"]["area_mode"].strip() if not kktix_area_mode in CONST_SELECT_OPTIONS_ARRAY: kktix_area_mode = CONST_SELECT_ORDER_DEFAULT if 'area_keyword' in config_dict["kktix"]: - kktix_area_keyword = config_dict["kktix"]["area_keyword"] - if kktix_area_keyword is None: - kktix_area_keyword = "" - kktix_area_keyword = kktix_area_keyword.strip() + kktix_area_keyword = config_dict["kktix"]["area_keyword"].strip() if 'date_keyword' in config_dict["kktix"]: - kktix_date_keyword = config_dict["kktix"]["date_keyword"] - if kktix_date_keyword is None: - kktix_date_keyword = "" - kktix_date_keyword = kktix_date_keyword.strip() + kktix_date_keyword = config_dict["kktix"]["date_keyword"].strip() if 'auto_guess_options' in config_dict["kktix"]: auto_guess_options = config_dict["kktix"]["auto_guess_options"] @@ -793,21 +844,16 @@ def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): area_auto_select_mode = CONST_SELECT_ORDER_DEFAULT if 'area_keyword_1' in config_dict["tixcraft"]["area_auto_select"]: - area_keyword_1 = config_dict["tixcraft"]["area_auto_select"]["area_keyword_1"] - area_keyword_1 = area_keyword_1.strip() + area_keyword_1 = config_dict["tixcraft"]["area_auto_select"]["area_keyword_1"].strip() if 'area_keyword_2' in config_dict["tixcraft"]["area_auto_select"]: - area_keyword_2 = config_dict["tixcraft"]["area_auto_select"]["area_keyword_2"] - area_keyword_2 = area_keyword_2.strip() - + area_keyword_2 = config_dict["tixcraft"]["area_auto_select"]["area_keyword_2"].strip() if 'area_keyword_3' in config_dict["tixcraft"]["area_auto_select"]: - area_keyword_3 = config_dict["tixcraft"]["area_auto_select"]["area_keyword_3"] - area_keyword_3 = area_keyword_3.strip() + area_keyword_3 = config_dict["tixcraft"]["area_auto_select"]["area_keyword_3"].strip() if 'area_keyword_4' in config_dict["tixcraft"]["area_auto_select"]: - area_keyword_4 = config_dict["tixcraft"]["area_auto_select"]["area_keyword_4"] - area_keyword_4 = area_keyword_4.strip() + area_keyword_4 = config_dict["tixcraft"]["area_auto_select"]["area_keyword_4"].strip() pass_1_seat_remaining_enable = False if 'pass_1_seat_remaining' in config_dict["tixcraft"]: @@ -828,7 +874,6 @@ def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): print("browser", browser) print("language", language) print("ticket_number", ticket_number) - print("facebook_account", facebook_account) # for kktix print("==[kktix]==") @@ -863,6 +908,14 @@ def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): else: print('config is none') + global lbl_homepage + global lbl_browser + global lbl_language + global lbl_ticket_number + + global lbl_kktix + global lbl_tixcraft + row_count = 0 frame_group_header = Frame(root) @@ -872,10 +925,6 @@ def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): lbl_homepage = Label(frame_group_header, text=translate[language_code]['homepage']) lbl_homepage.grid(column=0, row=group_row_count, sticky = E) - #global txt_homepage - #txt_homepage = Entry(root, width=20, textvariable = StringVar(root, value=homepage)) - #txt_homepage.grid(column=1, row=row_count) - global combo_homepage combo_homepage = ttk.Combobox(frame_group_header, state="readonly") combo_homepage['values']= ("https://kktix.com","https://tixcraft.com","https://www.indievox.com/","https://www.famiticket.com.tw","http://www.urbtix.hk/","https://www.cityline.com/") @@ -923,6 +972,7 @@ def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): global combo_ticket_number # for text format. + # PS: some user keyin wrong type. @_@; ''' global combo_ticket_number_value combo_ticket_number_value = StringVar(frame_group_header, value=ticket_number) @@ -930,26 +980,13 @@ def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): combo_ticket_number.grid(column=1, row=group_row_count, sticky = W) ''' combo_ticket_number = ttk.Combobox(frame_group_header, state="readonly") - combo_ticket_number['values']= ("1","2","3","4","5","6","7","8","9","10") + combo_ticket_number['values']= ("1","2","3","4","5","6","7","8","9","10","11","12") #combo_ticket_number.current(0) combo_ticket_number.set(ticket_number) combo_ticket_number.grid(column=1, row=group_row_count, sticky = W) frame_group_header.grid(column=0, row=row_count, sticky = W, padx=UI_PADDING_X) - ''' - row_count+=1 - - lbl_facebook_account = Label(root, text="Facebook Account") - lbl_facebook_account.grid(column=0, row=row_count, sticky = E) - - global txt_facebook_account - global txt_facebook_account_value - txt_facebook_account_value = StringVar(root, value=facebook_account) - txt_facebook_account = Entry(root, width=20, textvariable = txt_facebook_account_value) - txt_facebook_account.grid(column=1, row=row_count, sticky = W) - ''' - row_count+=1 # for sub group KKTix. @@ -1007,7 +1044,6 @@ def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): lbl_kktix_area_keyword.grid(column=0, row=group_row_count, sticky = E) global txt_kktix_area_keyword - global txt_kktix_area_keyword_value txt_kktix_area_keyword_value = StringVar(frame_group_kktix, value=kktix_area_keyword) txt_kktix_area_keyword = Entry(frame_group_kktix, width=20, textvariable = txt_kktix_area_keyword_value) txt_kktix_area_keyword.grid(column=1, row=group_row_count, sticky = W) @@ -1027,7 +1063,6 @@ def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): lbl_kktix_date_keyword.grid(column=0, row=group_row_count, sticky = E) global txt_kktix_date_keyword - global txt_kktix_date_keyword_value txt_kktix_date_keyword_value = StringVar(frame_group_kktix, value=kktix_date_keyword) txt_kktix_date_keyword = Entry(frame_group_kktix, width=20, textvariable = txt_kktix_date_keyword_value) txt_kktix_date_keyword.grid(column=1, row=group_row_count, sticky = W) @@ -1042,7 +1077,6 @@ def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): global txt_kktix_answer_dictionary global txt_kktix_answer_dictionary_index txt_kktix_answer_dictionary_index = group_row_count - global txt_kktix_answer_dictionary_value #txt_kktix_answer_dictionary_value = StringVar(frame_group_kktix, value=kktix_answer_dictionary) #txt_kktix_answer_dictionary = Entry(frame_group_kktix, width=20, textvariable = txt_kktix_answer_dictionary_value) #txt_kktix_answer_dictionary.grid(column=1, row=group_row_count, sticky = W) @@ -1117,7 +1151,6 @@ def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): lbl_date_keyword.grid(column=0, row=date_keyword_index, sticky = E) global txt_date_keyword - global txt_date_keyword_value txt_date_keyword_value = StringVar(frame_group_tixcraft, value=date_keyword) txt_date_keyword = Entry(frame_group_tixcraft, width=20, textvariable = txt_date_keyword_value) txt_date_keyword.grid(column=1, row=date_keyword_index, sticky = W) @@ -1161,7 +1194,6 @@ def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): lbl_area_keyword_1.grid(column=0, row=area_keyword_1_index, sticky = E) global txt_area_keyword_1 - global txt_area_keyword_1_value txt_area_keyword_1_value = StringVar(frame_group_tixcraft, value=area_keyword_1) txt_area_keyword_1 = Entry(frame_group_tixcraft, width=20, textvariable = txt_area_keyword_1_value) txt_area_keyword_1.grid(column=1, row=area_keyword_1_index, sticky = W) @@ -1176,7 +1208,6 @@ def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): lbl_area_keyword_2.grid(column=0, row=area_keyword_2_index, sticky = E) global txt_area_keyword_2 - global txt_area_keyword_2_value txt_area_keyword_2_value = StringVar(frame_group_tixcraft, value=area_keyword_2) txt_area_keyword_2 = Entry(frame_group_tixcraft, width=20, textvariable = txt_area_keyword_2_value) txt_area_keyword_2.grid(column=1, row=area_keyword_2_index, sticky = W) @@ -1191,7 +1222,6 @@ def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): lbl_area_keyword_3.grid(column=0, row=area_keyword_3_index, sticky = E) global txt_area_keyword_3 - global txt_area_keyword_3_value txt_area_keyword_3_value = StringVar(frame_group_tixcraft, value=area_keyword_3) txt_area_keyword_3 = Entry(frame_group_tixcraft, width=20, textvariable = txt_area_keyword_3_value) txt_area_keyword_3.grid(column=1, row=area_keyword_3_index, sticky = W) @@ -1206,7 +1236,6 @@ def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): lbl_area_keyword_4.grid(column=0, row=area_keyword_4_index, sticky = E) global txt_area_keyword_4 - global txt_area_keyword_4_value txt_area_keyword_4_value = StringVar(frame_group_tixcraft, value=area_keyword_4) txt_area_keyword_4 = Entry(frame_group_tixcraft, width=20, textvariable = txt_area_keyword_4_value) txt_area_keyword_4.grid(column=1, row=area_keyword_4_index, sticky = W) @@ -1266,6 +1295,85 @@ def PreferenctTab(root, config_dict, language_code, UI_PADDING_X): showHideBlocks(all_layout_visible=True) +def AdvancedTab(root, config_dict, language_code, UI_PADDING_X): + row_count = 0 + + frame_group_header = Frame(root) + group_row_count = 0 + + facebook_account = "" + play_captcha_sound = False + captcha_sound_filename_default = "ding-dong.mp3" + captcha_sound_filename = "" + + if 'advanced' in config_dict: + if 'facebook_account' in config_dict["advanced"]: + facebook_account = config_dict["advanced"]["facebook_account"].strip() + if 'play_captcha_sound' in config_dict["advanced"]: + if 'enable' in config_dict["advanced"]["play_captcha_sound"]: + play_captcha_sound = config_dict["advanced"]["play_captcha_sound"]["enable"] + if 'filename' in config_dict["advanced"]["play_captcha_sound"]: + captcha_sound_filename = config_dict["advanced"]["play_captcha_sound"]["filename"].strip() + + # for kktix + print("==[advanced]==") + print("facebook_account", facebook_account) + print("play_captcha_sound", play_captcha_sound) + print("captcha_sound_filename", captcha_sound_filename) + + + # assign default value. + if captcha_sound_filename is None: + captcha_sound_filename = "" + if len(captcha_sound_filename)==0: + captcha_sound_filename = captcha_sound_filename_default + + + global lbl_facebook_account + lbl_facebook_account = Label(frame_group_header, text=translate[language_code]['facebook_account']) + lbl_facebook_account.grid(column=0, row=group_row_count, sticky = E) + + global txt_facebook_account + txt_facebook_account_value = StringVar(frame_group_header, value=facebook_account) + txt_facebook_account = Entry(frame_group_header, width=20, textvariable = txt_facebook_account_value) + txt_facebook_account.grid(column=1, row=group_row_count, sticky = W) + + group_row_count +=1 + + global lbl_play_captcha_sound + lbl_play_captcha_sound = Label(frame_group_header, text=translate[language_code]['play_captcha_sound']) + lbl_play_captcha_sound.grid(column=0, row=group_row_count, sticky = E) + + global chk_state_play_captcha_sound + chk_state_play_captcha_sound = BooleanVar() + chk_state_play_captcha_sound.set(play_captcha_sound) + + global chk_play_captcha_sound + chk_play_captcha_sound = Checkbutton(frame_group_header, text=translate[language_code]['enable'], variable=chk_state_play_captcha_sound) + chk_play_captcha_sound.grid(column=1, row=group_row_count, sticky = W) + + group_row_count +=1 + + global lbl_captcha_sound_filename + lbl_captcha_sound_filename = Label(frame_group_header, text=translate[language_code]['captcha_sound_filename']) + lbl_captcha_sound_filename.grid(column=0, row=group_row_count, sticky = E) + + #print("captcha_sound_filename:", captcha_sound_filename) + global txt_captcha_sound_filename + txt_captcha_sound_filename_value = StringVar(frame_group_header, value=captcha_sound_filename) + txt_captcha_sound_filename = Entry(frame_group_header, width=20, textvariable = txt_captcha_sound_filename_value) + txt_captcha_sound_filename.grid(column=1, row=group_row_count, sticky = W) + + icon_play_filename = "icon_play_3.gif" + icon_play_img = PhotoImage(file=icon_play_filename) + + lbl_icon_play = Label(frame_group_header, image=icon_play_img, cursor="hand2") + lbl_icon_play.image = icon_play_img + lbl_icon_play.grid(column=3, row=group_row_count) + lbl_icon_play.bind("", lambda e: btn_preview_sound_clicked()) + + frame_group_header.grid(column=0, row=row_count) + def AboutTab(root, language_code): row_count = 0 @@ -1317,8 +1425,37 @@ def AboutTab(root, language_code): lbl_release_url.grid(column=1, row=group_row_count, sticky = W) lbl_release_url.bind("", lambda e: open_url(URL_RELEASE)) + group_row_count +=1 + + lbl_fb_fans = Label(frame_group_header, text=u'Facebook') + lbl_fb_fans.grid(column=0, row=group_row_count, sticky = E) + + lbl_fb_fans_url = Label(frame_group_header, text=URL_FB, fg="blue", cursor="hand2") + lbl_fb_fans_url.grid(column=1, row=group_row_count, sticky = W) + lbl_fb_fans_url.bind("", lambda e: open_url(URL_FB)) + frame_group_header.grid(column=0, row=row_count) +def get_action_bar(root,language_code): + frame_action = Frame(root) + + btn_run = ttk.Button(frame_action, text=translate[language_code]['run'], command=btn_run_clicked) + btn_run.grid(column=0, row=0) + + btn_save = ttk.Button(frame_action, text=translate[language_code]['save'], command=btn_save_clicked) + btn_save.grid(column=1, row=0) + + #btn_donate = ttk.Button(frame_action, text=translate[language_code]['donate'], command=btn_donate_clicked, width=5) + #btn_donate.grid(column=2, row=0) + + #btn_help = ttk.Button(frame_action, text=translate[language_code]['help'], command=btn_help_clicked) + #btn_help.grid(column=2, row=0) + + btn_exit = ttk.Button(frame_action, text=translate[language_code]['exit'], command=btn_exit_clicked) + btn_exit.grid(column=3, row=0) + + return frame_action + def main(): global translate translate = load_translate() @@ -1331,7 +1468,6 @@ def main(): root = Tk() root.title(CONST_APP_VERSION) - #style = ttk.Style(root) #style.theme_use('aqua') @@ -1353,29 +1489,15 @@ def main(): tab1 = Frame(tabControl) tabControl.add(tab1, text=translate[language_code]['preference']) tab2 = Frame(tabControl) - tabControl.add(tab2, text=translate[language_code]['about']) + tabControl.add(tab2, text=translate[language_code]['advanced']) + tab3 = Frame(tabControl) + tabControl.add(tab3, text=translate[language_code]['about']) tabControl.grid(column=0, row=row_count) tabControl.select(tab1) row_count+=1 - frame_action = Frame(root) - - btn_run = ttk.Button(frame_action, text=translate[language_code]['run'], command=btn_run_clicked) - btn_run.grid(column=0, row=0) - - btn_save = ttk.Button(frame_action, text=translate[language_code]['save'], command=btn_save_clicked) - btn_save.grid(column=1, row=0) - - #btn_donate = ttk.Button(frame_action, text=translate[language_code]['donate'], command=btn_donate_clicked, width=5) - #btn_donate.grid(column=2, row=0) - - #btn_help = ttk.Button(frame_action, text=translate[language_code]['help'], command=btn_help_clicked) - #btn_help.grid(column=2, row=0) - - btn_exit = ttk.Button(frame_action, text=translate[language_code]['exit'], command=btn_exit_clicked) - btn_exit.grid(column=3, row=0) - + frame_action = get_action_bar(root,language_code) frame_action.grid(column=0, row=row_count) global UI_PADDING_X @@ -1385,13 +1507,14 @@ def main(): GUI_SIZE_HEIGHT = 550 PreferenctTab(tab1, config_dict, language_code, UI_PADDING_X) - AboutTab(tab2, language_code) + AdvancedTab(tab2, config_dict, language_code, UI_PADDING_X) + AboutTab(tab3, language_code) GUI_SIZE_MACOS = str(GUI_SIZE_WIDTH) + 'x' + str(GUI_SIZE_HEIGHT) GUI_SIZE_WINDOWS=str(GUI_SIZE_WIDTH-60) + 'x' + str(GUI_SIZE_HEIGHT-90) GUI_SIZE =GUI_SIZE_MACOS - import platform + if platform.system() == 'Windows': GUI_SIZE = GUI_SIZE_WINDOWS