diff --git a/MAXBOT_ONLINE_ANSWER.txt b/MAXBOT_ONLINE_ANSWER.txt new file mode 100644 index 0000000..e69de29 diff --git a/chrome_tixcraft.py b/chrome_tixcraft.py index 2c63e13..8d9a954 100644 --- a/chrome_tixcraft.py +++ b/chrome_tixcraft.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 #encoding=utf-8 #執行方式:python chrome_tixcraft.py 或 python3 chrome_tixcraft.py +#import jieba +#from DrissionPage import ChromiumPage +import argparse +import base64 import json import logging import os @@ -8,11 +12,15 @@ import pathlib import platform import random import re +import ssl import sys import time -#import jieba +import warnings +import webbrowser from datetime import datetime +import chromedriver_autoinstaller_max +import requests from selenium import webdriver from selenium.common.exceptions import (NoAlertPresentException, NoSuchWindowException, @@ -24,58 +32,35 @@ from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import Select, WebDriverWait - -#from DrissionPage import ChromiumPage - -logging.basicConfig() -logger = logging.getLogger('logger') -import warnings - -# for check kktix reg_info -import requests from urllib3.exceptions import InsecureRequestWarning -warnings.simplefilter('ignore',InsecureRequestWarning) -import ssl - -ssl._create_default_https_context = ssl._create_unverified_context - -# ocr -import base64 +from NonBrowser import NonBrowser try: import ddddocr - from NonBrowser import NonBrowser except Exception as exc: pass -import argparse -import webbrowser +CONST_APP_VERSION = "MaxBot (2024.01.05)" -import chromedriver_autoinstaller_max - -CONST_APP_VERSION = "MaxBot (2024.01.04)" - -CONST_MAXBOT_CONFIG_FILE = "settings.json" -CONST_MAXBOT_LAST_URL_FILE = "MAXBOT_LAST_URL.txt" -CONST_MAXBOT_INT28_FILE = "MAXBOT_INT28_IDLE.txt" CONST_MAXBOT_ANSWER_ONLINE_FILE = "MAXBOT_ONLINE_ANSWER.txt" +CONST_MAXBOT_CONFIG_FILE = "settings.json" +CONST_MAXBOT_EXTENSION_NAME = "Maxbotplus_1.0.0" +CONST_MAXBOT_INT28_FILE = "MAXBOT_INT28_IDLE.txt" +CONST_MAXBOT_LAST_URL_FILE = "MAXBOT_LAST_URL.txt" CONST_MAXBOT_QUESTION_FILE = "MAXBOT_QUESTION.txt" -MAXBOT_EXTENSION_NAME = "Maxbotplus_1.0.0" - -CONST_HOMEPAGE_DEFAULT = "https://tixcraft.com" -URL_CHROME_DRIVER = 'https://chromedriver.chromium.org/' CONST_CHROME_VERSION_NOT_MATCH_EN="Please download the WebDriver version to match your browser version." CONST_CHROME_VERSION_NOT_MATCH_TW="請下載與您瀏覽器相同版本的WebDriver版本,或更新您的瀏覽器版本。" +CONST_CHROME_DRIVER_WEBSITE = 'https://chromedriver.chromium.org/' -CONST_KKTIX_SIGN_IN_URL = "https://kktix.com/users/sign_in?back_to=%s" -CONST_FAMI_SIGN_IN_URL = "https://www.famiticket.com.tw/Home/User/SignIn" CONST_CITYLINE_SIGN_IN_URL = "https://www.cityline.com/Login.html?targetUrl=https%3A%2F%2Fwww.cityline.com%2FEvents.html" -CONST_URBTIX_SIGN_IN_URL = "https://www.urbtix.hk/member-login" -CONST_KHAM_SIGN_IN_URL = "https://kham.com.tw/application/UTK13/UTK1306_.aspx" -CONST_TICKET_SIGN_IN_URL = "https://ticket.com.tw/application/utk13/utk1306_.aspx" +CONST_FAMI_SIGN_IN_URL = "https://www.famiticket.com.tw/Home/User/SignIn" CONST_HKTICKETING_SIGN_IN_URL = "https://premier.hkticketing.com/Secure/ShowLogin.aspx" +CONST_KHAM_SIGN_IN_URL = "https://kham.com.tw/application/UTK13/UTK1306_.aspx" +CONST_KKTIX_SIGN_IN_URL = "https://kktix.com/users/sign_in?back_to=%s" +CONST_TICKET_SIGN_IN_URL = "https://ticket.com.tw/application/utk13/utk1306_.aspx" +CONST_URBTIX_SIGN_IN_URL = "https://www.urbtix.hk/member-login" CONST_FROM_TOP_TO_BOTTOM = "from top to bottom" CONST_FROM_BOTTOM_TO_TOP = "from bottom to top" @@ -91,10 +76,14 @@ CONST_OCR_CAPTCH_IMAGE_SOURCE_CANVAS = "canvas" CONST_WEBDRIVER_TYPE_SELENIUM = "selenium" CONST_WEBDRIVER_TYPE_UC = "undetected_chromedriver" CONST_WEBDRIVER_TYPE_DP = "DrissionPage" -CONST_AUTO_RELOAD_RANDOM_DELAY_MAX_SECOND = 4 CONST_CHROME_FAMILY = ["chrome","edge","brave"] USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" +warnings.simplefilter('ignore',InsecureRequestWarning) +ssl._create_default_https_context = ssl._create_unverified_context +logging.basicConfig() +logger = logging.getLogger('logger') + def t_or_f(arg): ret = False ua = str(arg).upper() @@ -379,7 +368,7 @@ def is_all_alpha_or_numeric(text): def get_favoriate_extension_path(webdriver_path, config_dict): #print("webdriver_path:", webdriver_path) extension_list = [] - extension_list.append(os.path.join(webdriver_path, MAXBOT_EXTENSION_NAME + ".crx")) + extension_list.append(os.path.join(webdriver_path, CONST_MAXBOT_EXTENSION_NAME + ".crx")) return extension_list def get_chromedriver_path(webdriver_path): @@ -408,12 +397,10 @@ def get_brave_bin_path(): return brave_path def get_chrome_options(webdriver_path, config_dict): - browser=config_dict["browser"] - chrome_options = webdriver.ChromeOptions() - if browser=="edge": + if config_dict["browser"]=="edge": chrome_options = webdriver.EdgeOptions() - if browser=="safari": + if config_dict["browser"]=="safari": chrome_options = webdriver.SafariOptions() is_log_performace = False @@ -424,7 +411,8 @@ def get_chrome_options(webdriver_path, config_dict): break if is_log_performace: - chrome_options.set_capability("goog:loggingPrefs",{"performance": "ALL"}) + if config_dict["browser"] in CONST_CHROME_FAMILY: + chrome_options.set_capability("goog:loggingPrefs",{"performance": "ALL"}) # PS: this is crx version. extension_list = get_favoriate_extension_path(webdriver_path, config_dict) @@ -454,7 +442,7 @@ def get_chrome_options(webdriver_path, config_dict): if len(config_dict["advanced"]["proxy_server_port"]) > 2: chrome_options.add_argument('--proxy-server=%s' % config_dict["advanced"]["proxy_server_port"]) - if browser=="brave": + if config_dict["browser"]=="brave": brave_path = get_brave_bin_path() if os.path.exists(brave_path): chrome_options.binary_location = brave_path @@ -488,7 +476,7 @@ def load_chromdriver_normal(config_dict, driver_type): if not os.path.exists(chromedriver_path): print("Please download chromedriver and extract zip to webdriver folder from this url:") print("請下在面的網址下載與你chrome瀏覽器相同版本的chromedriver,解壓縮後放到webdriver目錄裡:") - print(URL_CHROME_DRIVER) + print(CONST_CHROME_DRIVER_WEBSITE) else: chrome_service = Service(chromedriver_path) chrome_options = get_chrome_options(webdriver_path, config_dict) @@ -592,14 +580,17 @@ def get_uc_options(uc, config_dict, webdriver_path): ext = ext.replace('.crx','') if os.path.exists(ext): # sync config. - if MAXBOT_EXTENSION_NAME in ext: + if CONST_MAXBOT_EXTENSION_NAME in ext: target_path = ext target_path = os.path.join(target_path, "data") - target_path = os.path.join(target_path, "settings.json") + target_path = os.path.join(target_path, CONST_MAXBOT_CONFIG_FILE) #print("save as to:", target_path) - if os.path.exists(target_path): - with open(target_path, 'w') as outfile: - json.dump(config_dict, outfile) + try: + os.unlink(target_path) + except Exception as exc: + pass + with open(target_path, 'w') as outfile: + json.dump(config_dict, outfile) load_extension_path += ("," + os.path.abspath(ext)) if len(load_extension_path) > 0: @@ -765,8 +756,6 @@ def get_driver_by_config(config_dict): # entry point if homepage is None: homepage = "" - if len(homepage) == 0: - homepage = CONST_HOMEPAGE_DEFAULT Root_Dir = get_app_root() webdriver_path = os.path.join(Root_Dir, "webdriver") @@ -3394,7 +3383,7 @@ def tixcraft_assign_ticket_number(driver, config_dict): def tixcraft_ticket_main(driver, config_dict, ocr, Captcha_Browser, domain_name): # use extension instead of selenium. # checkbox javascrit code at chrome extension. - if config_dict["browser"] in ["firefox", "edge", "safari"]: + if not config_dict["browser"] in CONST_CHROME_FAMILY: tixcraft_ticket_main_agree(driver, config_dict) is_ticket_number_assigned = False @@ -4712,7 +4701,7 @@ if (typeof $.kkUser.checked_status_register_code === 'undefined') { def kktix_reg_auto_reload(driver, url, config_dict): # auto reload javascrit code at chrome extension. - if config_dict["browser"] in ["firefox", "edge", "safari"]: + if not config_dict["browser"] in CONST_CHROME_FAMILY: kktix_check_register_status(driver, url) is_finish_checkbox_click = False @@ -12132,7 +12121,7 @@ def ticketplus_main(driver, url, config_dict, ocr, Captcha_Browser, ticketplus_d is_reloading = False # move below code to chrome extension. - if config_dict["browser"] in ["firefox", "edge", "safari"]: + if not config_dict["browser"] in CONST_CHROME_FAMILY: is_reloading = ticketplus_order_auto_reload_coming_soon(driver) if not is_reloading: diff --git a/config_launcher.py b/config_launcher.py index 8dd30cf..b8d154e 100644 --- a/config_launcher.py +++ b/config_launcher.py @@ -22,7 +22,7 @@ import sys import threading import webbrowser -CONST_APP_VERSION = "MaxBot (2024.01.04)" +CONST_APP_VERSION = "MaxBot (2024.01.05)" CONST_MAXBOT_LAUNCHER_FILE = "config_launcher.json" CONST_MAXBOT_CONFIG_FILE = "settings.json" diff --git a/settings.json b/settings.json index 0f53679..2f45fd1 100644 --- a/settings.json +++ b/settings.json @@ -1 +1 @@ -{"homepage": "https://tixcraft.com", "browser": "chrome", "language": "\u7e41\u9ad4\u4e2d\u6587", "ticket_number": 2, "ocr_captcha": {"enable": true, "beta": true, "force_submit": true, "image_source": "canvas"}, "webdriver_type": "undetected_chromedriver", "date_auto_select": {"enable": true, "date_keyword": "", "mode": "random"}, "area_auto_select": {"enable": true, "mode": "random", "area_keyword": ""}, "keyword_exclude": "\"\u8f2a\u6905\",\"\u8eab\u969c\",\"\u8eab\u5fc3 \u969c\u7919\",\"Restricted View\",\"\u71c8\u67f1\u906e\u853d\",\"\u8996\u7dda\u4e0d\u5b8c\u6574\"", "kktix": {"auto_press_next_step_button": true, "auto_fill_ticket_number": true}, "tixcraft": {"pass_date_is_sold_out": true, "auto_reload_coming_soon_page": true}, "advanced": {"play_captcha_sound": {"enable": true, "filename": "ding-dong.wav"}, "tixcraft_sid": "", "ibonqware": "", "facebook_account": "", "kktix_account": "", "fami_account": "", "cityline_account": "", "urbtix_account": "", "hkticketing_account": "", "kham_account": "", "ticket_account": "", "udn_account": "", "ticketplus_account": "", "facebook_password": "", "kktix_password": "", "fami_password": "", "urbtix_password": "", "cityline_password": "", "hkticketing_password": "", "kham_password": "", "ticket_password": "", "udn_password": "", "ticketplus_password": "", "disable_adjacent_seat": false, "hide_some_image": true, "block_facebook_network": false, "headless": false, "verbose": false, "auto_guess_options": true, "user_guess_string": "", "online_dictionary_url": "", "auto_reload_page_interval": 0.1, "proxy_server_port": ""}} \ No newline at end of file +{"homepage": "https://tixcraft.com", "browser": "chrome", "language": "\u7e41\u9ad4\u4e2d\u6587", "ticket_number": 2, "ocr_captcha": {"enable": true, "beta": true, "force_submit": true, "image_source": "canvas"}, "webdriver_type": "undetected_chromedriver", "date_auto_select": {"enable": true, "date_keyword": "", "mode": "random"}, "area_auto_select": {"enable": true, "mode": "random", "area_keyword": ""}, "keyword_exclude": "\"\u8f2a\u6905\",\"\u8eab\u969c\",\"\u8eab\u5fc3 \u969c\u7919\",\"Restricted View\",\"\u71c8\u67f1\u906e\u853d\",\"\u8996\u7dda\u4e0d\u5b8c\u6574\"", "kktix": {"auto_press_next_step_button": true, "auto_fill_ticket_number": true}, "tixcraft": {"pass_date_is_sold_out": true, "auto_reload_coming_soon_page": true}, "advanced": {"play_captcha_sound": {"enable": true, "filename": "ding-dong.wav"}, "tixcraft_sid": "", "ibonqware": "", "facebook_account": "", "kktix_account": "", "fami_account": "", "cityline_account": "", "urbtix_account": "", "hkticketing_account": "", "kham_account": "", "ticket_account": "", "udn_account": "", "ticketplus_account": "", "facebook_password": "", "kktix_password": "", "fami_password": "", "urbtix_password": "", "cityline_password": "", "hkticketing_password": "", "kham_password": "", "ticket_password": "", "udn_password": "", "ticketplus_password": "", "disable_adjacent_seat": false, "hide_some_image": false, "block_facebook_network": false, "headless": false, "verbose": false, "auto_guess_options": true, "user_guess_string": "", "remote_url": "\"http://127.0.0.1:16888/\"", "auto_reload_page_interval": 0.1, "proxy_server_port": ""}} \ No newline at end of file diff --git a/settings.py b/settings.py index 9515c47..5d1b455 100644 --- a/settings.py +++ b/settings.py @@ -13,11 +13,13 @@ except ImportError: from tkinter import messagebox from tkinter.filedialog import asksaveasfilename +import asyncio import base64 import json import os import platform import socket +import ssl import subprocess import sys import threading @@ -27,26 +29,26 @@ import webbrowser import pyperclip import requests +import tornado +from tornado.web import Application from urllib3.exceptions import InsecureRequestWarning -warnings.simplefilter('ignore',InsecureRequestWarning) -import ssl +try: + import ddddocr +except Exception as exc: + pass -ssl._create_default_https_context = ssl._create_unverified_context +CONST_APP_VERSION = "MaxBot (2024.01.05)" -CONST_APP_VERSION = "MaxBot (2024.01.04)" - -CONST_MAXBOT_CONFIG_FILE = "settings.json" -CONST_MAXBOT_LAST_URL_FILE = "MAXBOT_LAST_URL.txt" -CONST_MAXBOT_INT28_FILE = "MAXBOT_INT28_IDLE.txt" CONST_MAXBOT_ANSWER_ONLINE_FILE = "MAXBOT_ONLINE_ANSWER.txt" +CONST_MAXBOT_CONFIG_FILE = "settings.json" +CONST_MAXBOT_EXTENSION_NAME = "Maxbotplus_1.0.0" +CONST_MAXBOT_EXTENSION_STATUS_JSON = "status.json" +CONST_MAXBOT_INT28_FILE = "MAXBOT_INT28_IDLE.txt" +CONST_MAXBOT_LAST_URL_FILE = "MAXBOT_LAST_URL.txt" CONST_MAXBOT_QUESTION_FILE = "MAXBOT_QUESTION.txt" -MAXBOT_EXTENSION_NAME = "Maxbotplus_1.0.0" -MAXBOT_EXTENSION_STATUS_JSON = "status.json" - -CONST_SERVER_PORT_DEFAULT = 8888 -CONST_SERVER_PORT = CONST_SERVER_PORT_DEFAULT +CONST_SERVER_PORT = 16888 CONST_FROM_TOP_TO_BOTTOM = "from top to bottom" CONST_FROM_BOTTOM_TO_TOP = "from bottom to top" @@ -54,23 +56,6 @@ CONST_CENTER = "center" CONST_RANDOM = "random" CONST_SELECT_ORDER_DEFAULT = CONST_RANDOM CONST_SELECT_OPTIONS_DEFAULT = (CONST_FROM_TOP_TO_BOTTOM, CONST_FROM_BOTTOM_TO_TOP, CONST_CENTER, CONST_RANDOM) -CONST_ADBLOCK_PLUS_ADVANCED_FILTER_DEFAULT = '''tixcraft.com###topAlert -tixcraft.com##.col-md-7.col-xs-12.mgt-16.mx-auto -tixcraft.com##.col-md-7.col-xs-12.mgt-16.text-center -tixcraft.com##.footer.clearfix -tixcraft.com##.page-info.row.line-btm.mg-0 -tixcraft.com##.row.justify-content-start.navbar-location -tixcraft.com##.topBar.alert-box.emergency -||facebook.com/plugins/share_button.php -||google-analytics.com^ -||googletagmanager.com^ -||googletagservices.com^ -||play.google.com^ -||player.youku.com^ -||twitter.com^ -||youtube.com/iframe_api^ -||e2elog.fetnet.net^ -''' CONST_EXCLUDE_DEFAULT = "\"輪椅\",\"身障\",\"身心 障礙\",\"Restricted View\",\"燈柱遮蔽\",\"視線不完整\"" CONST_CAPTCHA_SOUND_FILENAME_DEFAULT = "ding-dong.wav" CONST_HOMEPAGE_DEFAULT = "https://tixcraft.com" @@ -81,7 +66,6 @@ CONST_OCR_CAPTCH_IMAGE_SOURCE_CANVAS = "canvas" CONST_WEBDRIVER_TYPE_SELENIUM = "selenium" CONST_WEBDRIVER_TYPE_UC = "undetected_chromedriver" CONST_WEBDRIVER_TYPE_DP = "DrissionPage" -USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" CONST_SUPPORTED_SITES = ["https://kktix.com" ,"https://tixcraft.com (拓元)" @@ -102,9 +86,9 @@ CONST_SUPPORTED_SITES = ["https://kktix.com" ,"https://ticketing.galaxymacau.com/ (澳門銀河)" ,"http://premier.ticketek.com.au" ] -# 目前機器人已失效, 因為官方的 reCaptcha 可以檢測出機器人。 -''' -''' + +warnings.simplefilter('ignore',InsecureRequestWarning) +ssl._create_default_https_context = ssl._create_unverified_context translate={} @@ -132,10 +116,13 @@ def load_translate(): en_us["and"] = 'And with' en_us["local_dictionary"] = 'Local Dictionary' - en_us["online_dictionary_url"] = 'Online Dictionary URL' + en_us["remote_url"] = 'Remote URL' + en_us["server_url"] = 'Server URL' en_us["auto_guess_options"] = 'Guess Options in Question' en_us["user_guess_string"] = 'Fill Answers in Question' en_us["preview"] = 'Preview' + en_us["question"] = 'Question' + en_us["answer"] = 'Answer' en_us["date_auto_select"] = 'Date Auto Select' en_us["date_select_order"] = 'Date select order' @@ -173,6 +160,7 @@ def load_translate(): en_us["preference"] = 'Preference' en_us["advanced"] = 'Advanced' en_us["verification_word"] = "Verification code" + en_us["maxbot_server"] = 'Server' en_us["autofill"] = 'Autofill' en_us["runtime"] = 'Runtime' en_us["about"] = 'About' @@ -235,10 +223,13 @@ def load_translate(): zh_tw["and"] = '而且(同列)' zh_tw["local_dictionary"] = '使用者自定字典' - zh_tw["online_dictionary_url"] = '線上字典檔網址' + zh_tw["remote_url"] = '遠端網址' + zh_tw["server_url"] = '伺服器網址' zh_tw["auto_guess_options"] = '自動猜測驗證問題' zh_tw["user_guess_string"] = '驗證問題中的答案清單' zh_tw["preview"] = '預覽' + zh_tw["question"] = '驗證問題' + zh_tw["answer"] = '答案' zh_tw["date_auto_select"] = '日期自動點選' zh_tw["date_select_order"] = '日期排序方式' @@ -275,6 +266,7 @@ def load_translate(): zh_tw["preference"] = '偏好設定' zh_tw["advanced"] = '進階設定' zh_tw["verification_word"] = "驗證問題" + zh_tw["maxbot_server"] = '伺服器' zh_tw["autofill"] = '自動填表單' zh_tw["runtime"] = '執行階段' zh_tw["about"] = '關於' @@ -338,10 +330,13 @@ def load_translate(): zh_cn["and"] = '而且(同列)' zh_cn["local_dictionary"] = '本地字典' - zh_cn["online_dictionary_url"] = '在线词典网址' + zh_cn["remote_url"] = '远端网址' + zh_cn["server_url"] = '服务器地址' zh_cn["auto_guess_options"] = '自动猜测验证问题' zh_cn["user_guess_string"] = '验证问题的答案列表' zh_cn["preview"] = '预览' + zh_cn["question"] = '验证问题' + zh_cn["answer"] = '答案' zh_cn["date_auto_select"] = '日期自动点选' zh_cn["date_select_order"] = '日期排序方式' @@ -378,6 +373,7 @@ def load_translate(): zh_cn["preference"] = '偏好设定' zh_cn["advanced"] = '进阶设定' zh_cn["verification_word"] = "验证字" + zh_cn["maxbot_server"] = '伺服器' zh_cn["autofill"] = '自动填表单' zh_cn["runtime"] = '运行' zh_cn["about"] = '关于' @@ -442,10 +438,13 @@ def load_translate(): ja_jp["and"] = 'そして(同列)' ja_jp["local_dictionary"] = 'ローカル辞書' - ja_jp["online_dictionary_url"] = 'オンライン辞書のURL' + ja_jp["remote_url"] = 'リモートURL' + ja_jp["server_url"] = 'サーバーURL' ja_jp["auto_guess_options"] = '自動推測検証問題' ja_jp["user_guess_string"] = '検証用の質問の回答リスト' ja_jp["preview"] = 'プレビュー' + ja_jp["question"] = '質問' + ja_jp["answer"] = '答え' ja_jp["date_auto_select"] = '日付自動選択' ja_jp["date_select_order"] = '日付のソート方法' @@ -482,6 +481,7 @@ def load_translate(): ja_jp["preference"] = '設定' ja_jp["advanced"] = '高度な設定' ja_jp["verification_word"] = "確認の言葉" + ja_jp["maxbot_server"] = 'サーバ' ja_jp["autofill"] = 'オートフィル' ja_jp["runtime"] = 'ランタイム' ja_jp["about"] = '情報' @@ -681,7 +681,7 @@ def get_default_config(): config_dict["advanced"]["verbose"] = False config_dict["advanced"]["auto_guess_options"] = True config_dict["advanced"]["user_guess_string"] = "" - config_dict["advanced"]["online_dictionary_url"] = "" + config_dict["advanced"]["remote_url"] = "http://127.0.0.1:%d/" % (CONST_SERVER_PORT) config_dict["advanced"]["auto_reload_page_interval"] = 0.1 config_dict["advanced"]["proxy_server_port"] = "" @@ -765,7 +765,7 @@ def btn_save_act(language_code, slience_mode=False): global chk_state_area_auto_select global txt_area_keyword global txt_keyword_exclude - global txt_online_dictionary_url + global txt_remote_url global combo_date_auto_select_mode global combo_area_auto_select_mode @@ -855,7 +855,7 @@ def btn_save_act(language_code, slience_mode=False): config_dict["date_auto_select"]["enable"] = bool(chk_state_date_auto_select.get()) config_dict["date_auto_select"]["mode"] = combo_date_auto_select_mode.get().strip() - + date_keyword = txt_date_keyword.get("1.0",END).strip() date_keyword = format_config_keyword_for_json(date_keyword) config_dict["date_auto_select"]["date_keyword"] = date_keyword @@ -872,8 +872,8 @@ def btn_save_act(language_code, slience_mode=False): user_guess_string = txt_user_guess_string.get("1.0",END).strip() user_guess_string = format_config_keyword_for_json(user_guess_string) - online_dictionary_url = txt_online_dictionary_url.get("1.0",END).strip() - online_dictionary_url = format_config_keyword_for_json(online_dictionary_url) + remote_url = txt_remote_url.get("1.0",END).strip() + remote_url = format_config_keyword_for_json(remote_url) # test keyword format. if is_all_data_correct: @@ -904,19 +904,19 @@ def btn_save_act(language_code, slience_mode=False): is_all_data_correct = False if is_all_data_correct: - if len(online_dictionary_url) > 0: + if len(remote_url) > 0: try: - test_array = json.loads("["+ online_dictionary_url +"]") + test_array = json.loads("["+ remote_url +"]") except Exception as exc: print(exc) - messagebox.showinfo(translate[language_code]["save"], "Error:" + translate[language_code]["online_dictionary_url"]) + messagebox.showinfo(translate[language_code]["save"], "Error:" + translate[language_code]["remote_url"]) is_all_data_correct = False if is_all_data_correct: config_dict["area_auto_select"]["area_keyword"] = area_keyword config_dict["keyword_exclude"] = keyword_exclude config_dict["advanced"]["user_guess_string"] = user_guess_string - config_dict["advanced"]["online_dictionary_url"] = online_dictionary_url + config_dict["advanced"]["remote_url"] = remote_url if is_all_data_correct: config_dict["area_auto_select"]["enable"] = bool(chk_state_area_auto_select.get()) @@ -1045,13 +1045,12 @@ def write_string_to_file(filename, data): if not outfile is None: outfile.write("%s" % data) -def save_url_to_file(new_online_dictionary_url, force_write = False): +def save_url_to_file(new_remote_url, force_write = False): html_text = "" - if len(new_online_dictionary_url) > 0: - headers = {"Accept-Language": "zh-TW,zh;q=0.5", 'User-Agent': USER_AGENT} + if len(new_remote_url) > 0: html_result = None try: - html_result = requests.get(new_online_dictionary_url , headers=headers, timeout=0.5, allow_redirects=False) + html_result = requests.get(new_remote_url , timeout=0.5, allow_redirects=False) except Exception as exc: html_result = None #print(exc) @@ -1076,17 +1075,17 @@ def save_url_to_file(new_online_dictionary_url, force_write = False): return is_write_to_file def btn_preview_text_clicked(): - global txt_online_dictionary_url - online_dictionary_url = "" + global txt_remote_url + remote_url = "" try: - online_dictionary_url = txt_online_dictionary_url.get("1.0",END).strip() + remote_url = txt_remote_url.get("1.0",END).strip() except Exception as exc: pass - online_dictionary_url = format_config_keyword_for_json(online_dictionary_url) - if len(online_dictionary_url) > 0: + remote_url = format_config_keyword_for_json(remote_url) + if len(remote_url) > 0: url_array = [] try: - url_array = json.loads("["+ online_dictionary_url +"]") + url_array = json.loads("["+ remote_url +"]") except Exception as exc: url_array = [] @@ -1094,7 +1093,7 @@ def btn_preview_text_clicked(): if len(url_array)==1: force_write = True for each_url in url_array: - #print("new_online_dictionary_url:", new_online_dictionary_url) + #print("new_remote_url:", new_remote_url) is_write_to_file = save_url_to_file(each_url, force_write=force_write) if is_write_to_file: break @@ -1144,19 +1143,9 @@ def run_python_script(script_name): pass def btn_open_text_server_clicked(): - global txt_online_dictionary_url - online_dictionary_url = "" - try: - online_dictionary_url = txt_online_dictionary_url.get("1.0",END).strip() - except Exception as exc: - pass - - if online_dictionary_url=="": - local_ip = get_ip_address() - ip_address = "http://%s:%d/" % (local_ip,CONST_SERVER_PORT) - txt_online_dictionary_url.insert("1.0", ip_address) - - run_python_script("text_server") + global tab4 + global tabControl + tabControl.select(tab4) def btn_preview_sound_clicked(): global txt_captcha_sound_filename @@ -1195,9 +1184,6 @@ def btn_donate_clicked(): def btn_help_clicked(): webbrowser.open(URL_HELP) -def btn_copy_clicked(): - pyperclip.copy(CONST_ADBLOCK_PLUS_ADVANCED_FILTER_DEFAULT) - def callbackLanguageOnChange(event): applyNewLanguage() @@ -1273,8 +1259,11 @@ def applyNewLanguage(): global chk_headless global chk_verbose - global lbl_online_dictionary_url + global lbl_remote_url + global lbl_server_url global lbl_online_dictionary_preview + global lbl_question + global lbl_answer global chk_auto_guess_options global tabControl @@ -1333,9 +1322,12 @@ def applyNewLanguage(): lbl_headless.config(text=translate[language_code]["headless"]) lbl_verbose.config(text=translate[language_code]["verbose"]) - lbl_online_dictionary_url.config(text=translate[language_code]["online_dictionary_url"]) + lbl_remote_url.config(text=translate[language_code]["remote_url"]) + lbl_server_url.config(text=translate[language_code]["server_url"]) lbl_online_dictionary_preview.config(text=translate[language_code]["preview"]) lbl_auto_guess_options.config(text=translate[language_code]["auto_guess_options"]) + lbl_question.config(text=translate[language_code]["question"]) + lbl_answer.config(text=translate[language_code]["answer"]) lbl_maxbot_status.config(text=translate[language_code]["running_status"]) lbl_maxbot_last_url.config(text=translate[language_code]["running_url"]) @@ -1361,9 +1353,10 @@ 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]["verification_word"]) - tabControl.tab(3, text=translate[language_code]["autofill"]) - tabControl.tab(4, text=translate[language_code]["runtime"]) - tabControl.tab(5, text=translate[language_code]["about"]) + tabControl.tab(3, text=translate[language_code]["maxbot_server"]) + tabControl.tab(4, text=translate[language_code]["autofill"]) + tabControl.tab(5, text=translate[language_code]["runtime"]) + tabControl.tab(6, text=translate[language_code]["about"]) global lbl_tixcraft_sid global lbl_ibon_ibonqware @@ -2114,14 +2107,14 @@ def VerificationTab(root, config_dict, language_code, UI_PADDING_X): group_row_count+=1 - global lbl_online_dictionary_url - lbl_online_dictionary_url = Label(frame_group_header, text=translate[language_code]['online_dictionary_url']) - lbl_online_dictionary_url.grid(column=0, row=group_row_count, sticky = E+N) + global lbl_remote_url + lbl_remote_url = Label(frame_group_header, text=translate[language_code]['remote_url']) + lbl_remote_url.grid(column=0, row=group_row_count, sticky = E+N) - global txt_online_dictionary_url - txt_online_dictionary_url = Text(frame_group_header, width=30, height=4) - txt_online_dictionary_url.grid(column=1, row=group_row_count, sticky = W) - txt_online_dictionary_url.insert("1.0", config_dict['advanced']["online_dictionary_url"].strip()) + global txt_remote_url + txt_remote_url = Text(frame_group_header, width=30, height=4) + txt_remote_url.grid(column=1, row=group_row_count, sticky = W) + txt_remote_url.insert("1.0", config_dict['advanced']["remote_url"].strip()) icon_preview_text_filename = "icon_chrome_4.gif" icon_preview_text_img = PhotoImage(file=icon_preview_text_filename) @@ -2159,6 +2152,70 @@ def VerificationTab(root, config_dict, language_code, UI_PADDING_X): frame_group_header.grid(column=0, row=row_count, padx=UI_PADDING_X) +def ServerTab(root, config_dict, language_code, UI_PADDING_X): + row_count = 0 + + frame_group_header = Frame(root) + group_row_count = 0 + + global lbl_server_url + lbl_server_url = Label(frame_group_header, text=translate[language_code]['server_url']) + lbl_server_url.grid(column=0, row=group_row_count, sticky = E) + + local_ip = get_ip_address() + ip_address = "http://%s:%d/" % (local_ip, CONST_SERVER_PORT) + global lbl_ip_address + lbl_ip_address = Label(frame_group_header, text=ip_address) + lbl_ip_address.grid(column=1, row=group_row_count, sticky = W) + + icon_copy_filename = "icon_copy_2.gif" + icon_copy_img = PhotoImage(file=icon_copy_filename) + + lbl_icon_copy_ip = Label(frame_group_header, image=icon_copy_img, cursor="hand2") + lbl_icon_copy_ip.image = icon_copy_img + lbl_icon_copy_ip.grid(column=2, row=group_row_count, sticky = W+N) + lbl_icon_copy_ip.bind("", lambda e: btn_copy_ip_clicked()) + + group_row_count += 1 + + global lbl_question + lbl_question = Label(frame_group_header, text=translate[language_code]['question']) + lbl_question.grid(column=0, row=group_row_count, sticky = E+N) + + global txt_question + txt_question = Text(frame_group_header, width=50, height=22) + txt_question.grid(column=1, row=group_row_count, sticky = W) + txt_question.insert("1.0", "") + + lbl_icon_copy_question = Label(frame_group_header, image=icon_copy_img, cursor="hand2") + lbl_icon_copy_question.image = icon_copy_img + #lbl_icon_copy_question.grid(column=2, row=group_row_count, sticky = W+N) + lbl_icon_copy_question.bind("", lambda e: btn_copy_question_clicked()) + + icon_query_filename = "icon_query_5.gif" + icon_query_img = PhotoImage(file=icon_query_filename) + + lbl_icon_query_question = Label(frame_group_header, image=icon_query_img, cursor="hand2") + lbl_icon_query_question.image = icon_query_img + lbl_icon_query_question.grid(column=2, row=group_row_count, sticky = W+N) + lbl_icon_query_question.bind("", lambda e: btn_query_question_clicked()) + + group_row_count += 1 + + global lbl_answer + lbl_answer = Label(frame_group_header, text=translate[language_code]['answer']) + lbl_answer.grid(column=0, row=group_row_count, sticky = E) + + global txt_answer + global txt_answer_value + txt_answer_value = StringVar(frame_group_header, value="") + txt_answer = Entry(frame_group_header, width=30, textvariable = txt_answer_value) + txt_answer.grid(column=1, row=group_row_count, sticky = W) + txt_answer.bind('', lambda e: btn_paste_answer_by_user()) + + frame_group_header.grid(column=0, row=row_count, padx=UI_PADDING_X, pady=15) + + def AutofillTab(root, config_dict, language_code, UI_PADDING_X): row_count = 0 @@ -2256,7 +2313,7 @@ def AutofillTab(root, config_dict, language_code, UI_PADDING_X): txt_cityline_password.grid(column=2, row=group_row_count, sticky = W) group_row_count +=1 - + global lbl_urbtix_account lbl_urbtix_account = Label(frame_group_header, text=translate[language_code]['urbtix_account']) lbl_urbtix_account.grid(column=0, row=group_row_count, sticky = E) @@ -2362,6 +2419,7 @@ def AutofillTab(root, config_dict, language_code, UI_PADDING_X): def resetful_api_timer(): while True: btn_preview_text_clicked() + preview_question_text_file() time.sleep(0.2) def settings_timer(): @@ -2372,9 +2430,9 @@ def settings_timer(): def clean_extension_status(): Root_Dir = get_app_root() webdriver_path = os.path.join(Root_Dir, "webdriver") - target_path = os.path.join(webdriver_path, MAXBOT_EXTENSION_NAME) + target_path = os.path.join(webdriver_path, CONST_MAXBOT_EXTENSION_NAME) target_path = os.path.join(target_path, "data") - target_path = os.path.join(target_path, MAXBOT_EXTENSION_STATUS_JSON) + target_path = os.path.join(target_path, CONST_MAXBOT_EXTENSION_STATUS_JSON) if os.path.exists(target_path): try: os.unlink(target_path) @@ -2385,9 +2443,9 @@ def clean_extension_status(): def sync_status_to_extension(status): Root_Dir = get_app_root() webdriver_path = os.path.join(Root_Dir, "webdriver") - target_path = os.path.join(webdriver_path, MAXBOT_EXTENSION_NAME) + target_path = os.path.join(webdriver_path, CONST_MAXBOT_EXTENSION_NAME) target_path = os.path.join(target_path, "data") - target_path = os.path.join(target_path, MAXBOT_EXTENSION_STATUS_JSON) + target_path = os.path.join(target_path, CONST_MAXBOT_EXTENSION_STATUS_JSON) #print("save as to:", target_path) status_json={} status_json["status"]=status @@ -2618,14 +2676,18 @@ def load_GUI(root, config_dict): tab3 = Frame(tabControl) tabControl.add(tab3, text=translate[language_code]['verification_word']) + global tab4 tab4 = Frame(tabControl) - tabControl.add(tab4, text=translate[language_code]['autofill']) + tabControl.add(tab4, text=translate[language_code]['maxbot_server']) tab5 = Frame(tabControl) - tabControl.add(tab5, text=translate[language_code]['runtime']) + tabControl.add(tab5, text=translate[language_code]['autofill']) tab6 = Frame(tabControl) - tabControl.add(tab6, text=translate[language_code]['about']) + tabControl.add(tab6, text=translate[language_code]['runtime']) + + tab7 = Frame(tabControl) + tabControl.add(tab7, text=translate[language_code]['about']) tabControl.grid(column=0, row=row_count) tabControl.select(tab1) @@ -2639,9 +2701,10 @@ def load_GUI(root, config_dict): PreferenctTab(tab1, config_dict, language_code, UI_PADDING_X) AdvancedTab(tab2, config_dict, language_code, UI_PADDING_X) VerificationTab(tab3, config_dict, language_code, UI_PADDING_X) - AutofillTab(tab4, config_dict, language_code, UI_PADDING_X) - RuntimeTab(tab5, config_dict, language_code, UI_PADDING_X) - AboutTab(tab6, language_code) + ServerTab(tab4, config_dict, language_code, UI_PADDING_X) + AutofillTab(tab5, config_dict, language_code, UI_PADDING_X) + RuntimeTab(tab6, config_dict, language_code, UI_PADDING_X) + AboutTab(tab7, language_code) def main(): @@ -2721,7 +2784,168 @@ def clean_tmp_file(): for filepath in remove_file_list: force_remove_file(filepath) +def get_ip_address(): + default_ip = "127.0.0.1" + ip = default_ip + try: + ip = [l for l in ([ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] + if not ip.startswith("127.")][:1], [[(s.connect(('8.8.8.8', 53)), + s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, + socket.SOCK_DGRAM)]][0][1]]) if l][0][0] + except Exception as exc: + print(exc) + try: + ip = socket.gethostname() + except Exception as exc2: + print(exc2) + ip = default_ip + return ip + +def btn_copy_ip_clicked(): + local_ip = get_ip_address() + ip_address = "http://%s:%d/" % (local_ip,CONST_SERVER_PORT) + pyperclip.copy(ip_address) + +def btn_copy_question_clicked(): + global txt_question + question_text = txt_question.get("1.0",END).strip() + if len(question_text) > 0: + pyperclip.copy(question_text) + +def btn_query_question_clicked(): + global txt_question + question_text = txt_question.get("1.0",END).strip() + if len(question_text) > 0: + webbrowser.open("https://www.google.com/search?q="+question_text) + +class MainHandler(tornado.web.RequestHandler): + def format_config_keyword_for_json(self, user_input): + if len(user_input) > 0: + if not ('\"' in user_input): + user_input = '"' + user_input + '"' + return user_input + + def compose_as_json(self, user_input): + user_input = self.format_config_keyword_for_json(user_input) + return "{\"data\":[%s]}" % user_input + + def get(self): + global txt_answer_value + answer_text = "" + try: + answer_text = txt_answer_value.get().strip() + except Exception as exc: + pass + answer_text_output = self.compose_as_json(answer_text) + #print("answer_text_output:", answer_text_output) + self.write(answer_text_output) + +class QuestionHandler(tornado.web.RequestHandler): + def get(self): + global txt_question + txt_question.insert("1.0", "") + +class VersionHandler(tornado.web.RequestHandler): + def get(self): + self.write({"version":self.application.version}) + + +class OcrHandler(tornado.web.RequestHandler): + def get(self): + self.write({"answer": "1234"}) + + def post(self): + self.set_header("Access-Control-Allow-Origin", "*") + self.set_header("Access-Control-Allow-Headers", "x-requested-with") + self.set_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS') + + _body = None + is_pass_check = True + errorMessage = "" + errorCode = 0 + + if is_pass_check: + is_pass_check = False + try : + _body = json.loads(self.request.body) + is_pass_check = True + except Exception: + errorMessage = "wrong json format" + errorCode = 1001 + pass + + img_base64 = None + image_data = "" + if is_pass_check: + if 'image_data' in _body: + image_data = _body['image_data'] + if len(image_data) > 0: + img_base64 = base64.b64decode(image_data) + else: + errorMessage = "image_data not exist" + errorCode = 1002 + + #print("is_pass_check:", is_pass_check) + #print("errorMessage:", errorMessage) + #print("errorCode:", errorCode) + ocr_answer = "" + if not img_base64 is None: + try: + ocr_answer = self.application.ocr.classification(img_base64) + print("ocr_answer:", ocr_answer) + except Exception as exc: + pass + + self.write({"answer": ocr_answer}) + +async def main_server(): + ocr = None + try: + ocr = ddddocr.DdddOcr(show_ad=False, beta=True) + except Exception as exc: + print(exc) + pass + + app = Application([ + (r"/", MainHandler), + (r"/version", VersionHandler), + (r"/ocr", OcrHandler), + (r"/query", MainHandler), + (r"/question", QuestionHandler), + ]) + app.ocr = ocr; + app.version = CONST_APP_VERSION; + + app.listen(CONST_SERVER_PORT) + await asyncio.Event().wait() + +def web_server(): + asyncio.run(main_server()) + +def preview_question_text_file(): + if os.path.exists(CONST_MAXBOT_QUESTION_FILE): + infile = None + if platform.system() == 'Windows': + infile = open(CONST_MAXBOT_QUESTION_FILE, 'r', encoding='UTF-8') + else: + infile = open(CONST_MAXBOT_QUESTION_FILE, 'r') + + if not infile is None: + question_text = infile.readline() + + global txt_question + try: + displayed_question_text = txt_question.get("1.0",END).strip() + if displayed_question_text != question_text: + # start to refresh + txt_question.delete("1.0","end") + if len(question_text) > 0: + txt_question.insert("1.0", question_text) + except Exception as exc: + pass + if __name__ == "__main__": threading.Thread(target=resetful_api_timer, daemon=True).start() + threading.Thread(target=web_server, daemon=True).start() clean_tmp_file() main() diff --git a/text_server.py b/text_server.py deleted file mode 100644 index 35b6d36..0000000 --- a/text_server.py +++ /dev/null @@ -1,258 +0,0 @@ -#!/usr/bin/env python -#encoding=utf-8 - -try: - # for Python2 - import tkMessageBox as messagebox - import ttk - from Tkinter import * -except ImportError: - # for Python3 - from tkinter import * - from tkinter import ttk - from tkinter import messagebox - from tkinter import filedialog - -import asyncio -import base64 -import os -import platform -import socket -import sys -import threading -import time -import webbrowser - -import pyperclip -import tornado -from tornado.web import Application - -CONST_APP_VERSION = "MaxBot (2024.01.04)" - -CONST_MAXBOT_QUESTION_FILE = "MAXBOT_QUESTION.txt" - -CONST_SERVER_PORT_DEFAULT = 8888 -CONST_SERVER_PORT = CONST_SERVER_PORT_DEFAULT - -def get_ip_address(): - default_ip = "127.0.0.1" - ip = default_ip - try: - ip = [l for l in ([ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] - if not ip.startswith("127.")][:1], [[(s.connect(('8.8.8.8', 53)), - s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, - socket.SOCK_DGRAM)]][0][1]]) if l][0][0] - except Exception as exc: - print(exc) - try: - ip = socket.gethostname() - except Exception as exc2: - print(exc2) - ip = default_ip - return ip - -def btn_copy_ip_clicked(): - local_ip = get_ip_address() - ip_address = "http://%s:%d/" % (local_ip,CONST_SERVER_PORT) - pyperclip.copy(ip_address) - -def btn_copy_question_clicked(): - global txt_question - question_text = txt_question.get("1.0",END).strip() - if len(question_text) > 0: - pyperclip.copy(question_text) - -def btn_query_question_clicked(): - global txt_question - question_text = txt_question.get("1.0",END).strip() - if len(question_text) > 0: - webbrowser.open("https://www.google.com/search?q="+question_text) - -def btn_paste_answer_by_user(): - print("btn_paste_answer_by_user") - -def TextInput(root, UI_PADDING_X): - row_count = 0 - - frame_group_header = Frame(root) - group_row_count = 0 - - global lbl_ip - lbl_ip = Label(frame_group_header, text="IP") - lbl_ip.grid(column=0, row=group_row_count, sticky = E) - - local_ip = get_ip_address() - ip_address = "http://%s:8888/" % (local_ip) - global lbl_ip_address - lbl_ip_address = Label(frame_group_header, text=ip_address) - lbl_ip_address.grid(column=1, row=group_row_count, sticky = W) - - icon_copy_filename = "icon_copy_2.gif" - icon_copy_img = PhotoImage(file=icon_copy_filename) - - lbl_icon_copy_ip = Label(frame_group_header, image=icon_copy_img, cursor="hand2") - lbl_icon_copy_ip.image = icon_copy_img - lbl_icon_copy_ip.grid(column=2, row=group_row_count, sticky = W+N) - lbl_icon_copy_ip.bind("", lambda e: btn_copy_ip_clicked()) - - group_row_count += 1 - - global lbl_question - lbl_question = Label(frame_group_header, text="Question") - lbl_question.grid(column=0, row=group_row_count, sticky = E+N) - - global txt_question - txt_question = Text(frame_group_header, width=50, height=22) - txt_question.grid(column=1, row=group_row_count, sticky = W) - txt_question.insert("1.0", "") - - lbl_icon_copy_question = Label(frame_group_header, image=icon_copy_img, cursor="hand2") - lbl_icon_copy_question.image = icon_copy_img - #lbl_icon_copy_question.grid(column=2, row=group_row_count, sticky = W+N) - lbl_icon_copy_question.bind("", lambda e: btn_copy_question_clicked()) - - icon_query_filename = "icon_query_5.gif" - icon_query_img = PhotoImage(file=icon_query_filename) - - lbl_icon_query_question = Label(frame_group_header, image=icon_query_img, cursor="hand2") - lbl_icon_query_question.image = icon_query_img - lbl_icon_query_question.grid(column=2, row=group_row_count, sticky = W+N) - lbl_icon_query_question.bind("", lambda e: btn_query_question_clicked()) - - group_row_count += 1 - - global lbl_answer - lbl_answer = Label(frame_group_header, text="Answer") - lbl_answer.grid(column=0, row=group_row_count, sticky = E) - - global txt_answer - global txt_answer_value - txt_answer_value = StringVar(frame_group_header, value="") - txt_answer = Entry(frame_group_header, width=30, textvariable = txt_answer_value) - txt_answer.grid(column=1, row=group_row_count, sticky = W) - txt_answer.bind('', lambda e: btn_paste_answer_by_user()) - - frame_group_header.grid(column=0, row=row_count, padx=UI_PADDING_X, pady=15) - - -def main_ui(): - global root - root = Tk() - root.title(CONST_APP_VERSION) - - global UI_PADDING_X - UI_PADDING_X = 20 - - TextInput(root, UI_PADDING_X) - - GUI_SIZE_WIDTH = 520 - GUI_SIZE_HEIGHT = 420 - - GUI_SIZE_MACOS = str(GUI_SIZE_WIDTH) + 'x' + str(GUI_SIZE_HEIGHT) - GUI_SIZE_WINDOWS=str(GUI_SIZE_WIDTH-30) + 'x' + str(GUI_SIZE_HEIGHT-35) - - GUI_SIZE =GUI_SIZE_MACOS - - if platform.system() == 'Windows': - GUI_SIZE = GUI_SIZE_WINDOWS - - root.geometry(GUI_SIZE) - - # for icon. - icon_filepath = 'tmp.ico' - - # icon format. - iconImg = 'AAABAAEAAAAAAAEAIAD4MgAAFgAAAIlQTkcNChoKAAAADUlIRFIAAAEAAAABAAgGAAAAXHKoZgAAAAFzUkdCAK7OHOkAAABQZVhJZk1NACoAAAAIAAIBEgADAAAAAQABAACHaQAEAAAAAQAAACYAAAAAAAOgAQADAAAAAQABAACgAgAEAAAAAQAAAQCgAwAEAAAAAQAAAQAAAAAAdTc0VwAAAVlpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KTMInWQAAMPFJREFUeAHtndmTJNd13rP36e7pWXt6OFiHGAxACssApChTskRZom1J3ETS9ov/AIclO/Tm8JMj/OxQBMNhO0IRdvjNtB0SSYirTFEiJZoSTBAidoAAZjCYAYazb73M9O7vdzKzOqu6qmvLyr5VeW5MT3VXVea997s3v3vOueecO/SlmYnNyIsj4AiUEoHhUvbaO+0IOAKGgBOATwRHoMQIOAGUePC9646AE4DPAUegxAg4AZR48L3rjoATgM8BR6DECDgBlHjwveuOgBOAzwFHoMQIOAGUePC9646AE4DPAUegxAg4AZR48L3rjoATgM8BR6DECDgBlHjwveuOgBOAzwFHoMQIOAGUePC9646AE4DPAUegxAg4AZR48L3rjoATgM8BR6DECDgBlHjwveuOgBOAzwFHoMQIOAGUePC9646AE4DPAUegxAg4AZR48L3rjoATgM8BR6DECDgBlHjwveuOgBOAzwFHoMQIOAGUePC9646AE4DPAUegxAg4AZR48L3rjoATgM8BR6DECDgBlHjwveuOgBOAzwFHoMQIOAGUePC9646AE4DPAUegxAg4AZR48L3rjoATgM8BR6DECDgBlHjwveuOgBOAzwFHoMQIOAGUePC9646AE4DPAUegxAg4AZR48L3rjoATgM8BR6DECDgBlHjwveuOgBOAzwFHoMQIOAGUePC9646AE4DPAUegxAg4AZR48L3rjoATgM8BR6DECDgBlHjwveuOgBOAzwFHoMQIOAGUePC9646AE4DPAUegxAg4AZR48L3rjoATgM8BR6DECDgBlHjwveuOgBOAzwFHoMQIOAGUePC9646AE4DPAUegxAg4AZR48L3rjoATgM8BR6DECDgBlHjwveuOgBOAzwFHoMQIOAGUePC9646AE4DPAUegxAg4AZR48L3rjoATgM8BR6DECIzuWt83N3et6oGpeGioflf6FFtmhPWoUb/q9zafd4VZpf587ljIXSpt7hCz4glADR0ZH49GJiai4dHRaGh4JErbbvOW/2wweAVD+68QMPupks2NzWhlfj7a3FivavbQ8HA0PjMjXPtRuBuK1u7eidbu3KnqUxF/jAmzkbFxVdVv820o2lhbjVYWFuy5aRer4glALYQAJg/PRlNzc9H00Q/o9aj9znt7Dh6MJvbtj8amp6ORPXs0KGPR0MiIloYhWx02Y5aAI+IO2y/tdru/v8/DffPM6eh7f/D70Z1r1wRNLAlsbmxEhz704eiTX/pP0fi+fR1NiN1EhnF+/X99OfrJl/6wMAJjPk3NHok++R//c3TgoRMi1I3dhKDtusHswt/+KPrBv/030cbqStvXF08AAnzl9u1oWT9MYiYvnRgeHYtG90zYgz8+s09EcCianD0cTR2Zi6ZEEtMpSRw5Ek3qs4n9B6KxvXuj0clJkyTa7nmfXzD/3nvRqlg/ffjpDpN37tRT0T2//CuFPUB5w3jw5MlC2765vh7d92ufiD7425+yhSnv/hRxv6uvvhJtSgropBRPALQyWc3TBjMI6/xI/Lt784Zmsj4RUcSrPd/XP6kKqAwj4yKJqSkTcycOHIgmDx2OJkUK00clRSREwd+8z+eQydjUpF1nkkRaaZ+/XnnpBROVs33i96NPfaTQByhvGMen91r7i1qJmUuPfPGf9u3DD/4LF96PNvT8dKL27Q4BNJo1Rgw87fEXkpfK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' - if platform.system() == 'Linux': - # PNG format. - iconImg = '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' - - tmpIcon = open(icon_filepath, 'wb+') - tmpIcon.write(base64.b64decode(iconImg)) - tmpIcon.close() - if platform.system() == 'Windows': - root.iconbitmap(icon_filepath) - if platform.system() == 'Darwin': - #from PIL import Image, ImageTk - #logo = ImageTk.PhotoImage(Image.open(icon_filepath).convert('RGB')) - #root.call('wm', 'iconphoto', root._w, logo) - pass - if platform.system() == 'Linux': - logo = PhotoImage(file=icon_filepath) - root.call('wm', 'iconphoto', root._w, logo) - os.remove(icon_filepath) - - root.mainloop() - - -class MainHandler(tornado.web.RequestHandler): - def format_config_keyword_for_json(self, user_input): - if len(user_input) > 0: - if not ('\"' in user_input): - user_input = '"' + user_input + '"' - return user_input - - def compose_as_json(self, user_input): - user_input = self.format_config_keyword_for_json(user_input) - return "{\"data\":[%s]}" % user_input - - def get(self): - global txt_answer_value - answer_text = "" - try: - answer_text = txt_answer_value.get().strip() - except Exception as exc: - pass - answer_text_output = self.compose_as_json(answer_text) - #print("answer_text_output:", answer_text_output) - self.write(answer_text_output) - -class QuestionHandler(tornado.web.RequestHandler): - def get(self): - global txt_question - txt_question.insert("1.0", "") - -async def main_server(): - app = Application([ - (r"/", MainHandler), - (r"/query", MainHandler), - (r"/question", QuestionHandler), - ]) - app.listen(CONST_SERVER_PORT) - await asyncio.Event().wait() - -def web_server(): - asyncio.run(main_server()) - -def preview_question_text_file(): - if os.path.exists(CONST_MAXBOT_QUESTION_FILE): - infile = None - if platform.system() == 'Windows': - infile = open(CONST_MAXBOT_QUESTION_FILE, 'r', encoding='UTF-8') - else: - infile = open(CONST_MAXBOT_QUESTION_FILE, 'r') - - if not infile is None: - question_text = infile.readline() - - global txt_question - try: - displayed_question_text = txt_question.get("1.0",END).strip() - if displayed_question_text != question_text: - # start to refresh - txt_question.delete("1.0","end") - if len(question_text) > 0: - txt_question.insert("1.0", question_text) - except Exception as exc: - pass - -def text_server_timer(): - while True: - preview_question_text_file() - time.sleep(0.2) - -if __name__ == "__main__": - threading.Thread(target=text_server_timer, daemon=True).start() - threading.Thread(target=web_server, daemon=True).start() - main_ui() - diff --git a/webdriver/Maxbotplus_1.0.0/background.js b/webdriver/Maxbotplus_1.0.0/background.js index 4c2c020..8f29bc9 100644 --- a/webdriver/Maxbotplus_1.0.0/background.js +++ b/webdriver/Maxbotplus_1.0.0/background.js @@ -5,13 +5,15 @@ 'use strict'; chrome.runtime.onInstalled.addListener(function(){ - console.log("onInstalled"); + //console.log("onInstalled"); let default_status='ON'; chrome.action.setBadgeText({ text: default_status }); + const default_webserver_runing=false; + fetch("data/settings.json") .then((resp) => resp.json()) .then((settings) => @@ -19,10 +21,11 @@ chrome.runtime.onInstalled.addListener(function(){ chrome.storage.local.set( { settings: settings, - status: default_status + status: default_status, + webserver_runing: default_webserver_runing } ); - console.log("dump settings.json to storage"); + console.log("dump settings.json to extension storage"); } ); }); @@ -56,7 +59,7 @@ chrome.action.onClicked.addListener(async (tab) => { { next_flag = false; } - console.log("next_flag:"+next_flag); + //console.log("next_flag:"+next_flag); set_status_to(next_flag); }); }); @@ -87,19 +90,78 @@ async function stopHeartbeat() startHeartbeat(); +async function ocr(data_url, image_data, tabId) +{ + //console.log("data_url:"+data_url); + fetch(data_url,{ + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + image_data: image_data + }) + }) + .then(response => + { + if (response.ok) + { + return response.json(); + } + else if (response.status === 404) + { + let result_json={"answer": "", "fail": 'error 404'}; + //console.log(result_json); + //sendResponse(result_json); + return Promise.reject('error 404') + } + else + { + let result_json={"answer": "", "fail": response.status}; + //console.log(result_json); + //sendResponse(result_json); + return Promise.reject('some other error: ' + response.status) + } + } + ) + .then((data) => + { + if (data) + { + let result_json=data; + console.log(result_json); + //sendResponse(result_json); + chrome.tabs.sendMessage(tabId, result_json); + } + } + ) + .catch(error => + { + //console.log('error is', error) + let result_json={"answer": "", "fail": error}; + //console.log(result_json); + //sendResponse(result_json); + } + ); +} + chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { let request_json = request; let result_json={"answer": "pong from background"}; - if(request_json.act=="decrypt") { + if(request_json.action=="decrypt") { console.log(typeof crypto_decrypt); let answer=""; if(typeof crypto_decrypt === 'function') { answer=crypto_decrypt(request_json.data.text,request_json.data.KEY,request_json.data.IV); } result_json={"answer": answer}; + sendResponse(result_json); + } + + if(request_json.action=="ocr") { + const tabId = sender.tab.id; + ocr(request_json.data.url, request_json.data.image_data, tabId); } - //let result = JSON.stringify(result_json); - sendResponse(result_json); }); diff --git a/webdriver/Maxbotplus_1.0.0/data/settings.json b/webdriver/Maxbotplus_1.0.0/data/settings.json index 0f53679..2f45fd1 100644 --- a/webdriver/Maxbotplus_1.0.0/data/settings.json +++ b/webdriver/Maxbotplus_1.0.0/data/settings.json @@ -1 +1 @@ -{"homepage": "https://tixcraft.com", "browser": "chrome", "language": "\u7e41\u9ad4\u4e2d\u6587", "ticket_number": 2, "ocr_captcha": {"enable": true, "beta": true, "force_submit": true, "image_source": "canvas"}, "webdriver_type": "undetected_chromedriver", "date_auto_select": {"enable": true, "date_keyword": "", "mode": "random"}, "area_auto_select": {"enable": true, "mode": "random", "area_keyword": ""}, "keyword_exclude": "\"\u8f2a\u6905\",\"\u8eab\u969c\",\"\u8eab\u5fc3 \u969c\u7919\",\"Restricted View\",\"\u71c8\u67f1\u906e\u853d\",\"\u8996\u7dda\u4e0d\u5b8c\u6574\"", "kktix": {"auto_press_next_step_button": true, "auto_fill_ticket_number": true}, "tixcraft": {"pass_date_is_sold_out": true, "auto_reload_coming_soon_page": true}, "advanced": {"play_captcha_sound": {"enable": true, "filename": "ding-dong.wav"}, "tixcraft_sid": "", "ibonqware": "", "facebook_account": "", "kktix_account": "", "fami_account": "", "cityline_account": "", "urbtix_account": "", "hkticketing_account": "", "kham_account": "", "ticket_account": "", "udn_account": "", "ticketplus_account": "", "facebook_password": "", "kktix_password": "", "fami_password": "", "urbtix_password": "", "cityline_password": "", "hkticketing_password": "", "kham_password": "", "ticket_password": "", "udn_password": "", "ticketplus_password": "", "disable_adjacent_seat": false, "hide_some_image": true, "block_facebook_network": false, "headless": false, "verbose": false, "auto_guess_options": true, "user_guess_string": "", "online_dictionary_url": "", "auto_reload_page_interval": 0.1, "proxy_server_port": ""}} \ No newline at end of file +{"homepage": "https://tixcraft.com", "browser": "chrome", "language": "\u7e41\u9ad4\u4e2d\u6587", "ticket_number": 2, "ocr_captcha": {"enable": true, "beta": true, "force_submit": true, "image_source": "canvas"}, "webdriver_type": "undetected_chromedriver", "date_auto_select": {"enable": true, "date_keyword": "", "mode": "random"}, "area_auto_select": {"enable": true, "mode": "random", "area_keyword": ""}, "keyword_exclude": "\"\u8f2a\u6905\",\"\u8eab\u969c\",\"\u8eab\u5fc3 \u969c\u7919\",\"Restricted View\",\"\u71c8\u67f1\u906e\u853d\",\"\u8996\u7dda\u4e0d\u5b8c\u6574\"", "kktix": {"auto_press_next_step_button": true, "auto_fill_ticket_number": true}, "tixcraft": {"pass_date_is_sold_out": true, "auto_reload_coming_soon_page": true}, "advanced": {"play_captcha_sound": {"enable": true, "filename": "ding-dong.wav"}, "tixcraft_sid": "", "ibonqware": "", "facebook_account": "", "kktix_account": "", "fami_account": "", "cityline_account": "", "urbtix_account": "", "hkticketing_account": "", "kham_account": "", "ticket_account": "", "udn_account": "", "ticketplus_account": "", "facebook_password": "", "kktix_password": "", "fami_password": "", "urbtix_password": "", "cityline_password": "", "hkticketing_password": "", "kham_password": "", "ticket_password": "", "udn_password": "", "ticketplus_password": "", "disable_adjacent_seat": false, "hide_some_image": false, "block_facebook_network": false, "headless": false, "verbose": false, "auto_guess_options": true, "user_guess_string": "", "remote_url": "\"http://127.0.0.1:16888/\"", "auto_reload_page_interval": 0.1, "proxy_server_port": ""}} \ No newline at end of file diff --git a/webdriver/Maxbotplus_1.0.0/js/kktix_registrations_assign.js b/webdriver/Maxbotplus_1.0.0/js/kktix_registrations_assign.js index 813e7df..8a76dc8 100644 --- a/webdriver/Maxbotplus_1.0.0/js/kktix_registrations_assign.js +++ b/webdriver/Maxbotplus_1.0.0/js/kktix_registrations_assign.js @@ -97,13 +97,19 @@ function begin() function dom_ready() { + let ret=false; //console.log("checking..."); if($("#settings").length>0) { - clearInterval(myInterval); + ret=true; + if(myInterval) clearInterval(myInterval); begin(); } + console.log("dom_ready:"+ret); + return ret; } -myInterval = setInterval(() => { - dom_ready(); -}, 100); \ No newline at end of file +if(!dom_ready()) { + myInterval = setInterval(() => { + dom_ready(); + }, 100); +} diff --git a/webdriver/Maxbotplus_1.0.0/js/ticketplus_order.js b/webdriver/Maxbotplus_1.0.0/js/ticketplus_order.js index 8c785cf..d96f7c0 100644 --- a/webdriver/Maxbotplus_1.0.0/js/ticketplus_order.js +++ b/webdriver/Maxbotplus_1.0.0/js/ticketplus_order.js @@ -86,7 +86,7 @@ async function decrypt_text(event_id, session_id) { const IV = '!@#$FETIXEVENTiv'; let bundle = { - act: 'decrypt', + action: 'decrypt', data: { 'KEY':KEY, 'IV':IV, @@ -100,7 +100,7 @@ async function decrypt_text(event_id, session_id) { //console.log(real_event_id); bundle = { - act: 'decrypt', + action: 'decrypt', data: { 'KEY':KEY, 'IV':IV, diff --git a/webdriver/Maxbotplus_1.0.0/js/tixcraft_ticket.js b/webdriver/Maxbotplus_1.0.0/js/tixcraft_ticket.js index 6721123..6aacd86 100644 --- a/webdriver/Maxbotplus_1.0.0/js/tixcraft_ticket.js +++ b/webdriver/Maxbotplus_1.0.0/js/tixcraft_ticket.js @@ -38,6 +38,80 @@ function assign_ticket_number(ticket_number) } } +var myInterval = null; + +function get_ocr_image() +{ + //console.log("get_ocr_image"); + let image_data = ""; + + // PS: tixcraft have different domain to use the same content script. + const currentUrl = window.location.href; + const domain = currentUrl.split('/')[2]; + + let image_id = 'TicketForm_verifyCode-image'; + let img = document.getElementById(image_id); + if(img!=null) { + let canvas = document.createElement('canvas'); + let context = canvas.getContext('2d'); + canvas.height = img.naturalHeight; + canvas.width = img.naturalWidth; + context.drawImage(img, 0, 0); + let img_data = canvas.toDataURL(); + if(img_data) { + image_data = img_data.split(",")[1]; + //console.log(image_data); + } + } + return image_data; +} + +chrome.runtime.onMessage.addListener((message) => { + //console.log('sent from background', message); + set_ocr_answer(message.answer); +}); + +function set_ocr_answer(answer) +{ + console.log("answer:"+answer); + if(answer.length > 0) { + $('#TicketForm_verifyCode').val(answer); + $("button[type='submit']").click(); + } +} + +async function get_ocr_answer(api_url, image_data) +{ + let bundle = { + action: 'ocr', + data: { + 'url': api_url + 'ocr', + 'image_data':image_data, + } + }; + + let bundle_string = JSON.stringify(bundle); + const return_answer = await chrome.runtime.sendMessage(bundle); + //console.log(return_answer); + + // fail due to CORS error + //ocr(bundle.data.url, bundle.data.image_data, bundle.data.callback); +} + +function orc_image_ready(api_url) +{ + let ret=false; + let image_data = get_ocr_image(); + if(image_data.length>0) { + ret=true; + if(myInterval) clearInterval(myInterval); + get_ocr_answer(api_url, image_data); + } + console.log("orc_image_ready:"+ret); + return ret; +} + + storage.get('settings', function (items) { if (items.settings) @@ -45,6 +119,21 @@ storage.get('settings', function (items) settings = items.settings; //console.log("ticket_number:"+ settings.ticket_number); assign_ticket_number(settings.ticket_number); + if(settings.ocr_captcha.enable) { + let remote_url_string = ""; + let remote_url_array = []; + if(settings.advanced.remote_url.length > 0) { + remote_url_array = JSON.parse('[' + settings.advanced.remote_url +']'); + } + if(remote_url_array.length) { + remote_url_string = remote_url_array[0]; + } + if(!orc_image_ready(remote_url_string)) { + myInterval = setInterval(() => { + orc_image_ready(remote_url_string); + }, 100); + } + } } else { console.log('no settings found'); } diff --git a/webdriver/Maxbotplus_1.0.0/manifest.json b/webdriver/Maxbotplus_1.0.0/manifest.json index 782665b..44d781d 100644 --- a/webdriver/Maxbotplus_1.0.0/manifest.json +++ b/webdriver/Maxbotplus_1.0.0/manifest.json @@ -28,6 +28,9 @@ "declarativeNetRequest", "declarativeNetRequestFeedback" ], + "host_permissions": [ + "http://127.0.0.1:16888/*" + ], "web_accessible_resources": [ { "resources": [ "data/*.json" ], diff --git a/webdriver/Maxbotplus_1.0.0/modules/heartbeatconnect.js b/webdriver/Maxbotplus_1.0.0/modules/heartbeatconnect.js index 94773d3..ba5c287 100644 --- a/webdriver/Maxbotplus_1.0.0/modules/heartbeatconnect.js +++ b/webdriver/Maxbotplus_1.0.0/modules/heartbeatconnect.js @@ -1,58 +1,30 @@ -const https_url="https://"; -const http_url="https://"; +const https_url = "https://"; +const http_url = "https://"; class HeartBeatConnector { - constructor() { - } - - start() { - //console.log("start heart beat connector"); - //load_font.loadFont(); + constructor() {} + start() + { sync_status_from_parent(); - - // Query the active tab before injecting the content script - /* - chrome.tabs.query( - { - active: true, - status: "complete", - currentWindow: true - }, (tabs) => - { - if(tabs && tabs.length) { - //console.log(tabs); - //console.log(tabs[0]); - if (tabs[0].url.startsWith(https_url) || tabs[0].url.startsWith(http_url)) { - // Use the Scripting API to execute a script - chrome.scripting.executeScript( - { - target: - { - tabId: tabs[0].id - }, - func: ack - } - ); - } - } - }); - */ } } function set_status_to(flag) { let nextState = 'ON'; - if(!flag) { + if (!flag) + { nextState = 'OFF'; } //console.log(nextState); - chrome.action.setBadgeText({ + chrome.action.setBadgeText( + { text: nextState - }); + } + ); chrome.storage.local.set( { @@ -61,34 +33,59 @@ function set_status_to(flag) ); } +function set_webserver_runing_to(flag) +{ + chrome.storage.local.set( + { + webserver_runing: flag + } + ); +} + function sync_status_from_parent() { //console.log("sync_status_from_parent"); let data_url = chrome.runtime.getURL("data/status.json"); fetch(data_url) - .then(response => { - if (response.ok) { - return response.json() - } else if(response.status === 404) { - return Promise.reject('error 404') - } else { - return Promise.reject('some other error: ' + response.status) + .then(response => + { + if (response.ok) + { + set_webserver_runing_to(true); + return response.json() } - }) + else if (response.status === 404) + { + set_webserver_runing_to(false); + return Promise.reject('error 404') + } + else + { + set_webserver_runing_to(false); + return Promise.reject('some other error: ' + response.status) + } + } + ) .then((data) => { - console.log(data); - if(data) { + //console.log(data); + if (data) + { set_status_to(data.status); } - }) - .catch(error => { + } + ) + .catch(error => + { //console.log('error is', error) - }); + set_webserver_runing_to(false); + } + ); } -function ack() { +function ack() +{ //console.log("act"); } diff --git a/webdriver/Maxbotplus_1.0.0/options.html b/webdriver/Maxbotplus_1.0.0/options.html index ea7bb82..98a89d3 100644 --- a/webdriver/Maxbotplus_1.0.0/options.html +++ b/webdriver/Maxbotplus_1.0.0/options.html @@ -76,6 +76,22 @@ +
+
+ + +
+
+ + + +
+ + +
+
diff --git a/webdriver/Maxbotplus_1.0.0/options.js b/webdriver/Maxbotplus_1.0.0/options.js index 3035fd1..50524b1 100644 --- a/webdriver/Maxbotplus_1.0.0/options.js +++ b/webdriver/Maxbotplus_1.0.0/options.js @@ -9,6 +9,8 @@ const area_keyword = document.querySelector('#area_keyword'); const keyword_exclude = document.querySelector('#keyword_exclude'); const auto_reload_page_interval = document.querySelector('#auto_reload_page_interval'); const disable_adjacent_seat = document.querySelector('#disable_adjacent_seat'); +const ocr_captcha_enable = document.querySelector('#ocr_captcha_enable'); +const remote_url = document.querySelector('#remote_url'); var settings = null; @@ -33,6 +35,15 @@ async function saveChanges() settings.keyword_exclude = keyword_exclude.value; settings.advanced.auto_reload_page_interval = auto_reload_page_interval.value; settings.advanced.disable_adjacent_seat = disable_adjacent_seat.checked; + settings.ocr_captcha.enable = ocr_captcha_enable.checked; + + let remote_url_array = []; + remote_url_array.push(remote_url.value); + let remote_url_string = JSON.stringify(remote_url_array); + remote_url_string = remote_url_string.substring(0,remote_url_string.length-1); + remote_url_string = remote_url_string.substring(1); + //console.log("final remote_url_string:"+remote_url_string); + settings.advanced.remote_url = remote_url_string; await storage.set( { @@ -61,6 +72,18 @@ function loadChanges() keyword_exclude.value = settings.keyword_exclude; auto_reload_page_interval.value = settings.advanced.auto_reload_page_interval; disable_adjacent_seat.checked = settings.advanced.disable_adjacent_seat; + ocr_captcha_enable.checked = settings.ocr_captcha.enable; + + let remote_url_string = ""; + let remote_url_array = []; + if(settings.advanced.remote_url.length > 0) { + remote_url_array = JSON.parse('[' + settings.advanced.remote_url +']'); + } + if(remote_url_array.length) { + remote_url_string = remote_url_array[0]; + } + remote_url.value = remote_url_string; + //message('Loaded saved settings.'); } else { console.log('no settings found');