diff --git a/.github/workflows/desktop-builds.yml b/.github/workflows/desktop-builds.yml new file mode 100644 index 0000000..1ea172c --- /dev/null +++ b/.github/workflows/desktop-builds.yml @@ -0,0 +1,181 @@ +name: Build and Release Executable + +on: + push: + branches: + - dev + +permissions: + contents: write + id-token: write + +env: + PYTHON_VERSION: 3.12.6 + +jobs: + build-windows: + runs-on: windows-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pyinstaller + pip install -r requirements.txt + + - name: Build executable with PyInstaller + run: pyinstaller --onefile --noconsole --icon=./GUI-ver/icon.ico ./GUI-ver/flet-ui.py + + - name: Zip executable and folders + run: | + mkdir release + cp -r ./dist/flet-ui.exe ./release/ + cp -r ./core ./release/ + cp -r ./GUI-ver/subs ./release/ + powershell Compress-Archive -Path ./release/* -DestinationPath ./release/XC-windows.zip + + - name: Upload Release Asset + uses: actions/upload-artifact@v3 + with: + name: windows-release + path: ./release/XC-windows.zip + + build-linux: + runs-on: ubuntu-latest + needs: build-windows + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pyinstaller + pip install -r requirements.txt + + - name: Build executable with PyInstaller + run: pyinstaller --onefile --noconsole --icon=./GUI-ver/icon.ico ./GUI-ver/flet-ui.py + + - name: Zip executable and folders + run: | + mkdir release + cp -r ./dist/flet-ui ./release/ + cp -r ./core ./release/ + cp -r ./GUI-ver/subs ./release/ + zip -r ./release/XC-linux.zip ./release/ + + - name: Upload Release Asset + uses: actions/upload-artifact@v3 + with: + name: linux-release + path: ./release/XC-linux.zip + + build-macos: + runs-on: macos-latest + needs: build-windows + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pyinstaller + pip install pillow + pip install -r requirements.txt + + - name: Build executable with PyInstaller + run: pyinstaller --onefile --noconsole --icon=./GUI-ver/icon.ico ./GUI-ver/flet-ui.py + + - name: Zip executable and folders + run: | + mkdir release + cp -r ./dist/flet-ui ./release/ + cp -r ./core ./release/ + cp -r ./GUI-ver/subs ./release/ + zip -r ./release/XC-macos.zip ./release/ + + - name: Upload Release Asset + uses: actions/upload-artifact@v3 + with: + name: macos-release + path: ./release/XC-macos.zip + + github-release: + name: Upload to GitHub Release + needs: [build-windows, build-linux, build-macos] + runs-on: ubuntu-latest + + permissions: + contents: write + id-token: write + + steps: + - name: Install GitHub CLI + run: sudo apt-get install gh + + - name: Download Release Assets + uses: actions/download-artifact@v3 + with: + name: windows-release + path: ./release/windows/ + + - name: Download Release Assets + uses: actions/download-artifact@v3 + with: + name: linux-release + path: ./release/linux/ + + - name: Download Release Assets + uses: actions/download-artifact@v3 + with: + name: macos-release + path: ./release/macos/ + + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: >- + gh release create + "v4.1-${{ github.run_number }}" + --repo "${{ github.repository }}" + --notes "" + --prerelease + --generate-notes + --title "Release v4.1-${{ github.run_number }}" + + - name: Upload artifacts to GitHub Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: >- + gh release upload + "v4.1-${{ github.run_number }}" + ./release/windows/XC-windows.zip + --repo "${{ github.repository }}" + + gh release upload + "v4.1-${{ github.run_number }}" + ./release/linux/XC-linux.zip + --repo "${{ github.repository }}" + + gh release upload + "v4.1-${{ github.run_number }}" + ./release/macos/XC-macos.zip + --repo "${{ github.repository }}" diff --git a/.gitignore b/.gitignore index 505e2e0..468dfdf 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ core/linux/select.txt gui/test3.py GUI-ver/test.py subs/3ircle -GUI-ver/subs/3ircle \ No newline at end of file +GUI-ver/subs/3ircle +core/win/select.txt diff --git a/GUI-ver/GUI.py b/GUI-ver/GUI.py index 625c269..671f617 100644 --- a/GUI-ver/GUI.py +++ b/GUI-ver/GUI.py @@ -48,7 +48,8 @@ def on_close(): import_page.config(bg="#0C0C0C") import_page.title("import profile") import_page.resizable(False, False) - +if os_sys == "win" : + import_page.iconbitmap(str(ASSETS_PATH) + "/icon.ico") name_label = Label(import_page,text="PROFILE NAME :",font=("calibri",15,"bold"),bg="#0C0C0C",fg="#ffffff") name_label.place(x=12,y=12) @@ -152,3 +153,5 @@ def on_close(): window.protocol("WM_DELETE_WINDOW", on_close) import_page.protocol("WM_DELETE_WINDOW", func=lambda :on_close_import(import_page,import_btn,url_var,name_var)) window.mainloop() + + diff --git a/GUI-ver/backendflet.py b/GUI-ver/backendflet.py new file mode 100644 index 0000000..8040211 --- /dev/null +++ b/GUI-ver/backendflet.py @@ -0,0 +1,180 @@ +import os +import json +import base64 +import requests +import subprocess +import platform +from pathlib import Path +import convert +import shutil +import time + + +class XrayBackend: + def __init__(self): + self.os_sys = self.os_det() + self.xray_process = None + self.version = "4.0" + self.xray_version = "1.8.24" + + def os_det(self): + system_os = platform.system() + if system_os == "Windows": + return "win" + elif system_os == "Linux": + return "linux" + elif system_os == "Darwin": + return "macos" + + def get_system_info(self): + return { + "OS": platform.system(), + "OS Version": platform.version(), + "Machine": platform.machine(), + "Processor": platform.processor() + } + + def get_profiles(self): + profiles = [] + path = "./subs" + if os.path.exists(path): + for sub in os.listdir(path): + if os.path.isdir(os.path.join(path, sub)): + profiles.append(sub) + return profiles + + def get_configs(self, profile): + configs = [] + path_json = f"./subs/{profile}/list.json" + if os.path.exists(path_json): + with open(path_json, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + for key, value in data.items(): + configs.append(f"{key} - {value}") + return configs + + def import_subscription(self, name, url): + headers = {"user-agent": "XC(Xray-Client)"} + r = requests.get(url=url, headers=headers) + text = r.text + decoded_bytes = base64.b64decode(text) + decoded_str = decoded_bytes.decode('utf-8') + list_configs = decoded_str.split("\n") + + directory_path = f"./subs/{name}" + Path(directory_path).mkdir(parents=True, exist_ok=True) + + dict_name = {} + for count, config in enumerate(list_configs): + if config.strip(): + config_json, config_name = convert.convert(config) + if config_name != "False": + with open(f"./subs/{name}/{count}.json", "w") as f: + f.write(config_json) + dict_name[count] = config_name + + with open(f"./subs/{name}/list.json", "w", encoding="utf-8") as f: + json.dump(dict_name, f, ensure_ascii=False, indent=4) + with open(f"./subs/{name}/url.txt", "w", encoding="utf-8") as f: + f.write(url) + + def update_subscription(self, profile): + path = f"./subs/{profile}/url.txt" + with open(path, "r") as f: + url = f.read().strip() + self.import_subscription(profile, url) + + def delete_subscription(self, profile): + directory_path = f"./subs/{profile}" + folder_path = Path(directory_path) + if folder_path.exists() and folder_path.is_dir(): + for filename in os.listdir(directory_path): + file_path = os.path.join(directory_path, filename) + try: + if os.path.isfile(file_path) or os.path.islink(file_path): + os.unlink(file_path) + elif os.path.isdir(file_path): + shutil.rmtree(file_path) + except Exception as e: + pass + # self.log(f"Error {file_path}: {e}") + + shutil.rmtree(directory_path) + + # self.log(f"Previous {profile} sub deleted") + + def ping_config(self, profile, config_num, ping_type): + config_path = f"./subs/{profile}/{config_num}.json" + + if ping_type == "Tcping": + try: + with open(config_path, 'r') as f: + config = json.load(f) + address = config['outbounds'][0]['settings']['vnext'][0]['address'] + ping_cmd = ['ping', '-n', '1', '-w', '1000', address] if self.os_sys == "win" else ['ping', '-c', '1', '-W', '1', address] + + if os.name == 'nt': + result = subprocess.Popen(ping_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, creationflags=subprocess.CREATE_NO_WINDOW) + else: + result = subprocess.Popen(ping_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + stdout, stderr = result.communicate() + if result.returncode == 0: + time_p = stdout.split('time=')[1].split()[0] + return f"{time_p}" + else: + return "Timeout" + + except Exception as e: + return f"Error: {str(e)}" + + elif ping_type == "Real-delay": + if self.xray_process: + self.stop_xray() + + self.run_xray(config_path) + time.sleep(2) + try: + s_time = time.time() + response = requests.get('http://gstatic.com/generate_204', proxies={"http": "http://127.0.0.1:1080"}) + e_time = time.time() + if 200 <= response.status_code < 300: + delay_ms = (e_time - s_time) * 1000 + return f"{delay_ms:.2f} ms" + else: + return "Timeout" + except : + self.stop_xray() + return f"Timeout" + finally: + self.stop_xray() + + + def run_xray(self, config_path): + try: + xray_path = f"./core/{self.os_sys}/xray" + creation_flags = subprocess.CREATE_NO_WINDOW if self.os_sys == "win" else 0 + self.xray_process = subprocess.Popen( + [xray_path, '-config', config_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + creationflags=creation_flags + ) + return f"Xray is running with config: {config_path}" + except Exception as e: + return f"Error starting Xray: {str(e)}" + + def stop_xray(self): + if self.xray_process: + self.xray_process.terminate() + self.xray_process = None + return "Xray has been stopped." + return "Xray is not running." + + def read_xray_logs(self): + while self.xray_process: + output = self.xray_process.stdout.readline() + if output == '' and self.xray_process.poll() is not None: + break + if output: + yield output.strip() \ No newline at end of file diff --git a/GUI-ver/flet-ui.py b/GUI-ver/flet-ui.py new file mode 100644 index 0000000..89f253a --- /dev/null +++ b/GUI-ver/flet-ui.py @@ -0,0 +1,394 @@ +import flet as ft +from backendflet import XrayBackend +import threading +import os +from collections import deque + +class XrayClientUI: + def __init__(self, page: ft.Page): + self.page = page + self.backend = XrayBackend() + self.page.title = "XC (Xray-Client)" + self.page.theme_mode = ft.ThemeMode.DARK + self.page.padding = 20 + self.selected_config = None + self.real_delay_stat = None + self.ping_all_button = None + self.cancel_real_delay_stat = "0" + self.ping_type = "Tcping" + self.tabs = ft.Tabs( + selected_index=0, + animation_duration=300, + tabs=[ft.Tab(text="Home")], + expand=1, + ) + self.log_buffer = deque(maxlen=1000) # Limit log entries + self.create_ui() + + def create_ui(self): + system_info = self.backend.get_system_info() + + settings_icon_value = ft.icons.SETTINGS if self.page.theme_mode == ft.ThemeMode.DARK else ft.icons.SETTINGS_OUTLINED + settings_icon = ft.IconButton( + icon=settings_icon_value, + icon_size=24, + on_click=self.show_settings_dialog, + style=ft.ButtonStyle( + color=ft.colors.WHITE if self.page.theme_mode == ft.ThemeMode.DARK else ft.colors.BLACK, + bgcolor=ft.colors.TRANSPARENT, + ), + tooltip="Settings" + ) + + import_button = ft.ElevatedButton( + "Import Subscription", + on_click=self.show_import_dialog, + style=ft.ButtonStyle( + color={ + ft.ControlState.DEFAULT: ft.colors.WHITE, + ft.ControlState.HOVERED: ft.colors.WHITE, + }, + bgcolor={ + ft.ControlState.DEFAULT: ft.colors.GREEN, + ft.ControlState.HOVERED: ft.colors.GREEN_700, + }, + ) + ) + + header = ft.Row( + controls=[import_button, ft.Container(expand=1), settings_icon], # container with expand fill the space bitween buttons + alignment=ft.MainAxisAlignment.SPACE_BETWEEN, # set two button in two side + ) + + info_card = ft.Card( + content=ft.Container( + content=ft.Column([ + ft.Text(f"XC (Xray-Client)", size=24, weight=ft.FontWeight.BOLD), + ft.Text(f"XC Version: {self.backend.version}", size=16), + ft.Text(f"Xray Version: {self.backend.xray_version}", size=16), + ft.Divider(), + *[ft.Text(f"{key}: {value}", size=14) for key, value in system_info.items()], + ]), + padding=20, + ), + elevation=5, + ) + + self.xray_button = ft.ElevatedButton( + "Start Xray", + on_click=self.toggle_xray, + style=ft.ButtonStyle( + color={ + ft.ControlState.DEFAULT: ft.colors.WHITE, + ft.ControlState.HOVERED: ft.colors.WHITE, + }, + bgcolor={ + ft.ControlState.DEFAULT: ft.colors.BLUE, + ft.ControlState.HOVERED: ft.colors.BLUE_700, + }, + ) + ) + + self.log_view = ft.TextField( + multiline=True, + read_only=True, + expand=True, + min_lines=10, + max_lines=20, + border_color=ft.colors.BLUE_200, + ) + + home_content = ft.Column([ + info_card, + ft.Container(height=20), + self.xray_button, + ft.Container(height=20), + ft.Text("Xray Logs:", size=18, weight=ft.FontWeight.BOLD), + self.log_view + ], expand=True, spacing=10) + + self.tabs.tabs[0].content = home_content + + for profile in self.backend.get_profiles(): + self.add_profile_tab(profile) + + # add header and tabs to page + self.page.add(header, self.tabs) + self.log("XC - Created By wikm , 3ircle with ❤️") + + + def add_profile_tab(self, profile): + configs = self.backend.get_configs(profile) + config_list = ft.ListView(expand=1, spacing=0, padding=20) + + for config in configs: + config_list.controls.append(self.create_config_tile_with_ping(config, profile)) + + update_button = ft.ElevatedButton("Update", on_click=lambda _: self.update_subscription(profile)) + delete_button = ft.ElevatedButton("Delete", on_click=lambda _: self.delete_subscription(profile)) + self.ping_all_button = ft.ElevatedButton( + "Ping All", + on_click=lambda _: self.ping_button(profile, config_list) + ) + + tab_content = ft.Column([ + ft.Row([update_button, delete_button, self.ping_all_button]), #ft.Row([update_button, delete_button, ping_all_button], alignment=ft.MainAxisAlignment.SPACE_BETWEEN) + config_list + ], expand=True) + + self.tabs.tabs.append(ft.Tab(text=profile, content=tab_content)) + self.page.update() + + def show_settings_dialog(self, e): + ping_type_dropdown = ft.Dropdown( + options=[ + ft.dropdown.Option("Real-delay"), + ft.dropdown.Option("Tcping"), + ], + value=self.ping_type, + on_change=self.change_ping_type, + expand=True, + ) + theme_dropdown = ft.Dropdown( + options=[ + ft.dropdown.Option("Dark"), + ft.dropdown.Option("Light"), + ], + value=self.page.theme_mode.name.capitalize(), + on_change=self.change_theme, + expand=True, + ) + + settings_dialog = ft.AlertDialog( + title=ft.Text("Settings"), + content=ft.Container( + content=ft.Column( + [ + ft.Row([ft.Text("Ping Type:"), ping_type_dropdown], + alignment=ft.MainAxisAlignment.SPACE_BETWEEN), + ft.Row([ft.Text("Theme:"), theme_dropdown], + alignment=ft.MainAxisAlignment.SPACE_BETWEEN), + ], + spacing=20, + ), + padding=20, + width=400, + ), + actions=[ft.TextButton("Close", on_click=lambda _: self.close_dialog(settings_dialog))], + ) + + # open setting page + self.page.overlay.append(settings_dialog) + settings_dialog.open = True + self.page.update() + + + def create_config_tile_with_ping(self, config, profile): + is_selected = config == self.selected_config + config_name = ft.Text(config, size=16, color=ft.colors.WHITE if self.page.theme_mode == ft.ThemeMode.DARK else ft.colors.BLACK) + ping_text = ft.Text("Ping: -", size=16, color=ft.colors.WHITE if self.page.theme_mode == ft.ThemeMode.DARK else ft.colors.BLACK) + + return ft.ListTile( + title=ft.Container( + content=ft.Text(config, size=16), + bgcolor=ft.colors.LIGHT_BLUE if is_selected else ft.colors.TRANSPARENT, + padding=ft.padding.all(10), + animate=ft.animation.Animation(duration=300, curve=ft.AnimationCurve.EASE_IN_OUT), + border_radius=10 + ), + trailing=ft.Container( + content=ping_text, + padding=ft.padding.all(10) + ), + selected=is_selected, + on_click=lambda _, c=config, p=profile: self.select_config(c, p) + ) + + + # "0" = cancel does not exist + # "1" = cancel showed + # "2" = camcel clicked + def ping_button(self, profile, config_list): + if self.cancel_real_delay_stat == "0": + self.ping_all_button.text = "Cancel" + self.cancel_real_delay_stat = "1" + self.ping_all_configs(profile, config_list) + elif self.cancel_real_delay_stat == "1": + self.ping_all_button.text = "Ping All" + self.cancel_real_delay_stat = "2" + + def ping_all_configs(self, profile, config_list): + if self.ping_type == "Real-delay" : + self.xray_button.disabled = True + self.xray_button.text = "Start Xray" + self.xray_button.style.bgcolor = { + ft.ControlState.DEFAULT: ft.colors.BLUE, + ft.ControlState.HOVERED: ft.colors.BLUE_700, + } + def ping_worker(): + self.cancel_real_delay_stat = "1" + for control in config_list.controls: + if isinstance(control, ft.ListTile): + config_name = control.title.content.value + ping_text = control.trailing.content + config_num = config_name.split("-")[0].strip() + result = self.backend.ping_config(profile, config_num , self.ping_type) + + if self.cancel_real_delay_stat == "2": + self.xray_button.disabled = False + break + + ping_text.value = f"Ping: {result}" + self.page.update() + + # reset button txt + self.ping_all_button.text = "Ping All" + self.cancel_real_delay_stat = "0" + self.page.update() + + threading.Thread(target=ping_worker, daemon=True).start() + + # def on_hover_row(self, e, config): + # container = e.control + # if e.data == "true" and container.bgcolor != ft.colors.BLUE_GREY: + # container.bgcolor = ft.colors.BLUE_GREY + # container.border_radius = 10 + # self.page.update() + # elif e.data == "false" and container.bgcolor != (ft.colors.LIGHT_BLUE if config == self.selected_config else ft.colors.TRANSPARENT): + # container.bgcolor = ft.colors.LIGHT_BLUE if config == self.selected_config else ft.colors.TRANSPARENT + # container.border_radius = 10 + # self.page.update() + + def select_config(self, config, profile): + self.selected_config = config + try: + config_index = int(config.split("-")[0]) + with open(f"./core/{self.backend.os_sys}/select.txt", "w") as f: + f.write(f"./subs/{profile}/{config_index}.json") + self.log(f"Config {config_index} selected.") + except Exception as e: + self.log(f"Error selecting config: {str(e)}") + self.refresh_profile_tab(profile) + + def refresh_profile_tab(self, profile): + if profile == "all": + profiles = self.backend.get_profiles() + for profile in profiles: + for tab in self.tabs.tabs: + if tab.text == profile: + configs = self.backend.get_configs(profile) + config_list = tab.content.controls[1] + config_list.controls = [self.create_config_tile_with_ping(config, profile) for config in configs] + + for tab in self.tabs.tabs: + if tab.text == profile: + configs = self.backend.get_configs(profile) + config_list = tab.content.controls[1] + for control in config_list.controls: + config_name = control.title.content.value + # change color to selcted + if config_name == self.selected_config: + control.title.content.bgcolor = ft.colors.LIGHT_BLUE + else: + control.title.content.bgcolor = ft.colors.TRANSPARENT + # just add new config + for config in configs: + if not any(control.title.content.value == config for control in config_list.controls): + config_list.controls.append(self.create_config_tile_with_ping(config, profile)) + + break + self.page.update() + + def show_import_dialog(self, e): + def import_sub(e): + name = name_field.value + url = url_field.value + if name and url: + self.backend.import_subscription(name, url) + self.add_profile_tab(name) + dialog.open = False + self.page.update() + + name_field = ft.TextField(label="Profile Name") + url_field = ft.TextField(label="URL") + dialog = ft.AlertDialog( + title=ft.Text("Import Subscription"), + content=ft.Column([name_field, url_field], tight=True), + actions=[ + ft.TextButton("Cancel", on_click=lambda _: self.close_dialog(dialog)), + ft.TextButton("Import", on_click=import_sub), + ], + ) + self.page.overlay.append(dialog) + dialog.open = True + self.page.update() + + def close_dialog(self, dialog): + dialog.open = False + self.page.update() + + def toggle_xray(self, e): + if self.backend.xray_process is None: + config_path = f"./core/{self.backend.os_sys}/select.txt" + if os.path.exists(config_path): + with open(config_path, "r") as f: + selected_config = f.read().strip() + message = self.backend.run_xray(selected_config) + self.xray_button.text = "Stop Xray" + self.xray_button.style.bgcolor = { + ft.ControlState.DEFAULT: ft.colors.RED, + ft.ControlState.HOVERED: ft.colors.RED_700, + } + self.log(message) + threading.Thread(target=self.read_xray_logs, daemon=True).start() + else: + self.log("No config selected. Please select a config first.") + else: + message = self.backend.stop_xray() + self.xray_button.text = "Start Xray" + self.xray_button.style.bgcolor = { + ft.ControlState.DEFAULT: ft.colors.BLUE, + ft.ControlState.HOVERED: ft.colors.BLUE_700, + } + self.log(message) + self.page.update() + + def read_xray_logs(self): + for log_line in self.backend.read_xray_logs(): + self.log(log_line) + + def change_theme(self, e): + selected_theme = e.control.value + # change theme + if selected_theme == "Dark": + self.page.theme_mode = ft.ThemeMode.DARK + else: + self.page.theme_mode = ft.ThemeMode.LIGHT + self.page.update() + self.refresh_profile_tab(profile="all") + print(f"Theme changed to: {selected_theme}") + + def change_ping_type(self, e): + selected_ping_type = e.control.value + # select ping type + if selected_ping_type == "Real-delay": + self.ping_type = "Real-delay" + else: + self.ping_type = "Tcping" + print(f"Ping type changed to: {self.ping_type}") + + def log(self, message): + self.log_buffer.append(message) + self.log_view.value = "\n".join(self.log_buffer) + self.page.update() + def update_subscription(self , profile) : + self.backend.update_subscription(profile) + self.refresh_profile_tab(profile) + def delete_subscription(self , profile) : + self.backend.delete_subscription(profile) + self.tabs.tabs = [tab for tab in self.tabs.tabs if tab.text != profile] + +def main(page: ft.Page): + XrayClientUI(page) + +ft.app(target=main) \ No newline at end of file diff --git a/GUI-ver/ping_proto.py b/GUI-ver/ping_proto.py new file mode 100644 index 0000000..4949670 --- /dev/null +++ b/GUI-ver/ping_proto.py @@ -0,0 +1,16 @@ +import requests +import time + +def Ping(proxy_port): + try : + s_time = time.time() + response = requests.get('http://gstatic.com/generate_204', proxies={"http":f"http://127.0.0.1:{proxy_port}"}) + e_time = time.time() + except : + return -1 + if response.status_code <300 and response.status_code > 199: + return e_time - s_time + else: + return -1 + +print(Ping(1080)) diff --git a/dist/.exist b/dist/.exist new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/dist/.exist @@ -0,0 +1 @@ + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..82b97d2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +flet==0.24.1 +Requests==2.32.3 diff --git a/screenshot/1.png b/screenshot/1.png new file mode 100644 index 0000000..32a65bc Binary files /dev/null and b/screenshot/1.png differ diff --git a/screenshot/2.png b/screenshot/2.png new file mode 100644 index 0000000..762be93 Binary files /dev/null and b/screenshot/2.png differ diff --git a/screenshot/3.png b/screenshot/3.png new file mode 100644 index 0000000..1421a0f Binary files /dev/null and b/screenshot/3.png differ diff --git a/screenshot/4.png b/screenshot/4.png new file mode 100644 index 0000000..5746475 Binary files /dev/null and b/screenshot/4.png differ diff --git a/screenshot/5.png b/screenshot/5.png new file mode 100644 index 0000000..b1806ee Binary files /dev/null and b/screenshot/5.png differ diff --git a/screenshot/6.png b/screenshot/6.png new file mode 100644 index 0000000..0d131e4 Binary files /dev/null and b/screenshot/6.png differ