1
0

feat:tkinter

This commit is contained in:
guorong.zheng 2024-06-09 19:16:40 +08:00
parent af0fd78c09
commit e8a33f28e4
8 changed files with 308 additions and 38 deletions

@ -8,6 +8,7 @@ on:
- master
- dev
- gd
- gd-test
jobs:
push:
runs-on: ${{ matrix.operating-system }}

@ -26,13 +26,13 @@ jobs:
run: pipenv install pyinstaller
- name: Build the application
run: pipenv run pyinstaller --onefile --windowed --add-data demo.txt:. --add-data config.py:. --add-data fofa_map.py:. --add-data main.py:. --add-data utils.py:. --name update-tool tkinter_ui.py
run: pipenv run pyinstaller tkinter_ui.spec
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: update-tool
path: dist/tkinter_ui
path: dist/update-tool.exe
- name: Install jq
run: sudo apt-get install jq
@ -59,3 +59,13 @@ jobs:
body: ${{ fromJSON(env.changelog) }}
draft: false
prerelease: false
- name: Upload Release Asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./dist/update-tool.exe
asset_name: update-tool.exe
asset_content_type: application/octet-stream

@ -19,6 +19,7 @@ aiohttp = ">=3.9.4"
bs4 = ">=0.0.2"
tqdm = ">=4.66.3"
async-timeout = ">=4.0.3"
pyinstaller = "*"
[requires]
python_version = ">=3.11"

76
Pipfile.lock generated

@ -1,11 +1,11 @@
{
"_meta": {
"hash": {
"sha256": "8bcaa60bc11c1b7a2f0403744cfa8cf993bcbc3edb74caa34cb8b4446d96b36d"
"sha256": "1d2e1a0c3c519496b2182c0a06934b3805decf830897194c0987ad52521c472d"
},
"pipfile-spec": 6,
"requires": {
"python_version": ">=3.11"
"python_version": ">=3.6"
},
"sources": [
{
@ -107,6 +107,13 @@
"markers": "python_version >= '3.7'",
"version": "==1.3.1"
},
"altgraph": {
"hashes": [
"sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406",
"sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"
],
"version": "==0.17.4"
},
"async-timeout": {
"hashes": [
"sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f",
@ -522,6 +529,22 @@
"markers": "python_version >= '3.7'",
"version": "==1.3.0.post0"
},
"packaging": {
"hashes": [
"sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5",
"sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"
],
"markers": "python_version >= '3.7'",
"version": "==24.0"
},
"pefile": {
"hashes": [
"sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc",
"sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6"
],
"markers": "sys_platform == 'win32'",
"version": "==2023.2.7"
},
"pycparser": {
"hashes": [
"sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6",
@ -530,6 +553,33 @@
"markers": "python_version >= '3.8'",
"version": "==2.22"
},
"pyinstaller": {
"hashes": [
"sha256:000c36b13fe4cd8d0d8c2bc855b1ddcf39867b5adf389e6b5ca45b25fa3e619d",
"sha256:1c3060a263758cf7f0144ab4c016097b20451b2469d468763414665db1bb743d",
"sha256:2b71509468c811968c0b5decb5bbe85b6292ea52d7b1f26313d2aabb673fa9a5",
"sha256:355832a3acc7de90a255ecacd4b9f9e166a547a79c8905d49f14e3a75c1acdb9",
"sha256:39ac424d2ee2457d2ab11a5091436e75a0cccae207d460d180aa1fcbbafdd528",
"sha256:3f4b6520f4423fe19bcc2fd63ab7238851ae2bdcbc98f25bc5d2f97cc62012e9",
"sha256:5ff6bc2784c1026f8e2f04aa3760cbed41408e108a9d4cf1dd52ee8351a3f6e1",
"sha256:6303c7a009f47e6a96ef65aed49f41e36ece8d079b9193ca92fe807403e5fe80",
"sha256:81cccfa9b16699b457f4788c5cc119b50f3cd4d0db924955f15c33f2ad27a50d",
"sha256:d257f6645c7334cbd66f38a4fac62c3ad614cc46302b2b5d9f8cc48c563bce0e",
"sha256:fe0af018d7d5077180e3144ada89a4da5df8d07716eb7e9482834a56dc57a4e8",
"sha256:ff31c5b99e05a4384bbe2071df67ec8b2b347640a375eae9b40218be2f1754c6"
],
"index": "pypi",
"markers": "python_version < '3.13' and python_version >= '3.8'",
"version": "==6.8.0"
},
"pyinstaller-hooks-contrib": {
"hashes": [
"sha256:8bf0775771fbaf96bcd2f4dfd6f7ae6c1dd1b1efe254c7e50477b3c08e7841d8",
"sha256:fd5f37dcf99bece184e40642af88be16a9b89613ecb958a8bd1136634fc9fac5"
],
"markers": "python_version >= '3.7'",
"version": "==2024.7"
},
"pysocks": {
"hashes": [
"sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299",
@ -546,6 +596,14 @@
"index": "pypi",
"version": "==2024.1"
},
"pywin32-ctypes": {
"hashes": [
"sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60",
"sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"
],
"markers": "sys_platform == 'win32'",
"version": "==0.2.2"
},
"requests": {
"hashes": [
"sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760",
@ -572,6 +630,14 @@
"markers": "python_version >= '3' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==1.0.6"
},
"setuptools": {
"hashes": [
"sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4",
"sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"
],
"markers": "python_version >= '3.8'",
"version": "==70.0.0"
},
"sgmllib3k": {
"hashes": [
"sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9"
@ -628,11 +694,11 @@
},
"typing-extensions": {
"hashes": [
"sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a",
"sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"
"sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d",
"sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"
],
"markers": "python_version >= '3.8'",
"version": "==4.12.1"
"version": "==4.12.2"
},
"urllib3": {
"extras": [

27
main.py

@ -146,15 +146,8 @@ class UpdateSource:
)
def write_channel_to_file(self):
total = len(
[
name
for channel_obj in self.channel_data.values()
for name in channel_obj.keys()
]
)
self.pbar = tqdm(total=total)
self.pbar.set_description(f"Writing, {total} channels remaining")
self.pbar = tqdm(total=self.total)
self.pbar.set_description(f"Writing, {self.total} channels remaining")
self.start_time = time()
for cate, channel_obj in self.channel_items.items():
for name in channel_obj.keys():
@ -243,6 +236,7 @@ class UpdateSource:
update_file(user_log_file, "result_new.log")
print(f"Update completed! Please check the {user_final_file} file!")
self.update_progress(f"更新完成, 请检查{user_final_file}文件", 100, True)
self.stop()
except asyncio.exceptions.CancelledError:
print("Update cancelled!")
@ -258,11 +252,16 @@ class UpdateSource:
format="%(message)s",
level=logging.INFO,
)
loop = asyncio.get_event_loop()
asyncio.set_event_loop(loop)
self.thread = threading.Thread(
target=loop.run_until_complete, args=(self.main(),)
)
def run_loop():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
loop.run_until_complete(self.main())
finally:
loop.close()
self.thread = threading.Thread(target=run_loop, daemon=True)
self.thread.start()
if not self.run_ui:
self.thread.join()

@ -23,6 +23,31 @@ class TkinterUI:
self.root.title("直播源接口更新工具")
self.update_source = UpdateSource()
self.update_running = False
self.config_entrys = [
"source_file_entry",
"source_file_button",
"final_file_entry",
"final_file_button",
"open_subscribe_checkbutton",
"open_multicast_checkbutton",
"open_online_search_checkbutton",
"open_sort_checkbutton",
"favorite_list_text",
"favorite_page_num_entry",
"default_page_num_entry",
"urls_limit_entry",
"response_time_weight_entry",
"resolution_weight_entry",
"ipv_type_combo",
"recent_days_entry",
"domain_blacklist_text",
"url_keywords_blacklist_text",
"subscribe_urls_text",
"region_list_text",
]
def format_list(self, text):
return [f"{item.strip()}" for item in text.split(",") if item.strip()]
def select_source_file(self):
filepath = filedialog.askopenfilename(
@ -31,7 +56,7 @@ class TkinterUI:
if filepath:
self.source_file_entry.delete(0, tk.END)
self.source_file_entry.insert(0, filepath)
self.save_config(False)
config.source_file = f'"{filepath}"'
def select_final_file(self):
filepath = filedialog.askopenfilename(
@ -40,12 +65,70 @@ class TkinterUI:
if filepath:
self.final_file_entry.delete(0, tk.END)
self.final_file_entry.insert(0, filepath)
self.save_config(False)
config.final_file = f'"{filepath}"'
def format_list(self, text):
return [f"{item.strip()}" for item in text.split(",") if item.strip()]
def update_open_subscribe(self):
config.open_subscribe = self.open_subscribe_var.get()
def save_config(self, tip=True):
def update_open_multicast(self):
config.open_multicast = self.open_multicast_var.get()
def update_open_online_search(self):
config.open_online_search = self.open_online_search_var.get()
def update_open_sort(self):
config.open_sort = self.open_sort_var.get()
def update_favorite_list(self, event):
config.favorite_list = self.format_list(
self.favorite_list_text.get(1.0, tk.END)
)
def update_favorite_page_num(self, event):
config.favorite_page_num = self.favorite_page_num_entry.get()
def update_default_page_num(self, event):
config.default_page_num = self.default_page_num_entry.get()
def update_urls_limit(self, event):
config.urls_limit = self.urls_limit_entry.get()
def update_response_time_weight(self, event):
config.response_time_weight = self.response_time_weight_entry.get()
def update_resolution_weight(self, event):
config.resolution_weight = self.resolution_weight_entry.get()
def update_ipv_type(self, event):
config.ipv_type = f'"{self.ipv_type_combo.get()}"'
def update_recent_days(self, event):
config.recent_days = self.recent_days_entry.get()
def update_url_keywords_blacklist(self, event):
config.url_keywords_blacklist = self.format_list(
self.url_keywords_blacklist_text.get(1.0, tk.END)
)
def update_domain_blacklist(self, event):
config.domain_blacklist = self.format_list(
self.domain_blacklist_text.get(1.0, tk.END)
)
def update_url_keywords_blacklist(self, event):
config.url_keywords_blacklist = self.format_list(
self.url_keywords_blacklist_text.get(1.0, tk.END)
)
def update_subscribe_urls(self, event):
config.subscribe_urls = self.format_list(
self.subscribe_urls_text.get(1.0, tk.END)
)
def update_region_list(self, event):
config.region_list = self.format_list(self.region_list_text.get(1.0, tk.END))
def save_config(self):
config_values = {
"source_file": f'"{self.source_file_entry.get()}"',
"final_file": f'"{self.final_file_entry.get()}"',
@ -78,16 +161,19 @@ class TkinterUI:
user_config_file = (
"user_config.py" if os.path.exists("user_config.py") else "config.py"
)
with open(resource_path(user_config_file, True), "w", encoding="utf-8") as f:
with open(
resource_path(user_config_file, persistent=True), "w", encoding="utf-8"
) as f:
for key, value in config_values.items():
f.write(f"{key} = {value}\n")
if tip:
messagebox.showinfo("提示", "保存成功")
messagebox.showinfo("提示", "保存成功")
def run_update(self):
self.update_running = not self.update_running
if self.update_running:
self.run_button.config(text="取消更新", state="normal")
for entry in self.config_entrys:
getattr(self, entry).config(state="disabled")
self.progress_bar["value"] = 0
self.progress_label.pack()
self.progress_bar.pack()
@ -95,6 +181,8 @@ class TkinterUI:
else:
self.update_source.stop()
self.run_button.config(text="开始更新", state="normal")
for entry in self.config_entrys:
getattr(self, entry).config(state="normal")
self.progress_bar.pack_forget()
self.progress_label.pack_forget()
@ -104,6 +192,9 @@ class TkinterUI:
self.root.update()
if finished:
self.run_button.config(text="开始更新", state="normal")
self.update_running = False
for entry in self.config_entrys:
getattr(self, entry).config(state="normal")
def init_UI(self):
@ -123,6 +214,7 @@ class TkinterUI:
variable=self.open_subscribe_var,
onvalue=True,
offvalue=False,
command=self.update_open_subscribe,
)
self.open_subscribe_checkbutton.pack(side=tk.LEFT, padx=4, pady=4)
@ -135,6 +227,7 @@ class TkinterUI:
variable=self.open_multicast_var,
onvalue=True,
offvalue=False,
command=self.update_open_multicast,
)
self.open_multicast_checkbutton.pack(side=tk.LEFT, padx=4, pady=4)
@ -156,6 +249,7 @@ class TkinterUI:
variable=self.open_online_search_var,
onvalue=True,
offvalue=False,
command=self.update_open_online_search,
)
self.open_online_search_checkbutton.pack(side=tk.LEFT, padx=4, pady=4)
@ -168,6 +262,7 @@ class TkinterUI:
variable=self.open_sort_var,
onvalue=True,
offvalue=False,
command=self.update_open_sort,
)
self.open_sort_checkbutton.pack(side=tk.LEFT, padx=4, pady=4)
@ -210,6 +305,7 @@ class TkinterUI:
side=tk.LEFT, padx=4, pady=4, expand=True, fill=tk.BOTH
)
self.favorite_list_text.insert(tk.END, ",".join(config.favorite_list))
self.favorite_list_text.bind("<KeyRelease>", self.update_favorite_list)
row5 = tk.Frame(self.root)
row5.pack(fill=tk.X)
@ -225,6 +321,7 @@ class TkinterUI:
self.favorite_page_num_entry = tk.Entry(row5_column1)
self.favorite_page_num_entry.pack(side=tk.LEFT, padx=4, pady=4)
self.favorite_page_num_entry.insert(0, config.favorite_page_num)
self.favorite_page_num_entry.bind("<KeyRelease>", self.update_favorite_page_num)
self.default_page_num_label = tk.Label(
row5_column2, text="默认获取页数:", width=14
@ -233,6 +330,7 @@ class TkinterUI:
self.default_page_num_entry = tk.Entry(row5_column2)
self.default_page_num_entry.pack(side=tk.LEFT, padx=4, pady=4)
self.default_page_num_entry.insert(0, config.default_page_num)
self.default_page_num_entry.bind("<KeyRelease>", self.update_default_page_num)
row6 = tk.Frame(self.root)
row6.pack(fill=tk.X)
@ -248,6 +346,7 @@ class TkinterUI:
self.urls_limit_entry = tk.Entry(row6_column1)
self.urls_limit_entry.pack(side=tk.LEFT, padx=4, pady=4)
self.urls_limit_entry.insert(15, config.urls_limit)
self.urls_limit_entry.bind("<KeyRelease>", self.update_urls_limit)
self.ipv_type_label = tk.Label(row6_column2, text="接口协议类型:", width=14)
self.ipv_type_label.pack(side=tk.LEFT, padx=4, pady=4)
@ -255,7 +354,7 @@ class TkinterUI:
self.ipv_type_combo.pack(side=tk.LEFT, padx=4, pady=4)
self.ipv_type_combo["values"] = ("ipv4", "ipv6", "all")
self.ipv_type_combo.current(0)
self.ipv_type_combo.bind("<<ComboboxSelected>>", lambda event: config.ipv_type)
self.ipv_type_combo.bind("<<ComboboxSelected>>", self.update_ipv_type)
row7 = tk.Frame(self.root)
row7.pack(fill=tk.X)
@ -271,6 +370,9 @@ class TkinterUI:
self.response_time_weight_entry = tk.Entry(row7_column1)
self.response_time_weight_entry.pack(side=tk.LEFT, padx=4, pady=4)
self.response_time_weight_entry.insert(0, config.response_time_weight)
self.response_time_weight_entry.bind(
"<KeyRelease>", self.update_response_time_weight
)
self.resolution_weight_label = tk.Label(
row7_column2, text="分辨率权重:", width=14
@ -279,6 +381,7 @@ class TkinterUI:
self.resolution_weight_entry = tk.Entry(row7_column2)
self.resolution_weight_entry.pack(side=tk.LEFT, padx=4, pady=4)
self.resolution_weight_entry.insert(0, config.resolution_weight)
self.resolution_weight_entry.bind("<KeyRelease>", self.update_resolution_weight)
row8 = tk.Frame(self.root)
row8.pack(fill=tk.X)
@ -288,6 +391,7 @@ class TkinterUI:
self.recent_days_entry = tk.Entry(row8)
self.recent_days_entry.pack(side=tk.LEFT, padx=4, pady=4)
self.recent_days_entry.insert(30, config.recent_days)
self.recent_days_entry.bind("<KeyRelease>", self.update_recent_days)
row9 = tk.Frame(self.root)
row9.pack(fill=tk.X)
@ -299,6 +403,7 @@ class TkinterUI:
side=tk.LEFT, padx=4, pady=4, expand=True, fill=tk.BOTH
)
self.domain_blacklist_text.insert(tk.END, ",".join(config.domain_blacklist))
self.domain_blacklist_text.bind("<KeyRelease>", self.update_domain_blacklist)
row10 = tk.Frame(self.root)
row10.pack(fill=tk.X)
@ -314,6 +419,9 @@ class TkinterUI:
self.url_keywords_blacklist_text.insert(
tk.END, ",".join(config.url_keywords_blacklist)
)
self.url_keywords_blacklist_text.bind(
"<KeyRelease>", self.update_url_keywords_blacklist
)
row11 = tk.Frame(self.root)
row11.pack(fill=tk.X)
@ -325,6 +433,7 @@ class TkinterUI:
side=tk.LEFT, padx=4, pady=4, expand=True, fill=tk.BOTH
)
self.subscribe_urls_text.insert(tk.END, ",".join(config.subscribe_urls))
self.subscribe_urls_text.bind("<KeyRelease>", self.update_subscribe_urls)
row12 = tk.Frame(self.root)
row12.pack(fill=tk.X)
@ -336,6 +445,7 @@ class TkinterUI:
side=tk.LEFT, padx=4, pady=4, expand=True, fill=tk.BOTH
)
self.region_list_text.insert(tk.END, ",".join(config.region_list))
self.region_list_text.bind("<KeyRelease>", self.update_region_list)
row13 = tk.Frame(self.root)
row13.pack(fill=tk.X, pady=10, padx=120)

38
tkinter_ui.spec Normal file

@ -0,0 +1,38 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['tkinter_ui.py'],
pathex=[],
binaries=[],
datas=[('demo.txt', '.')],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='update-tool',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

@ -1,7 +1,3 @@
try:
import user_config as config
except ImportError:
import config
from selenium import webdriver
import aiohttp
import asyncio
@ -23,6 +19,53 @@ from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import concurrent.futures
import sys
import importlib.util
def resource_path(relative_path, persistent=False):
"""
Get the resource path
"""
base_path = os.path.abspath(".")
total_path = os.path.join(base_path, relative_path)
if persistent:
return total_path
if os.path.exists(total_path):
return total_path
else:
try:
base_path = sys._MEIPASS
return os.path.join(base_path, relative_path)
except Exception:
return total_path
def load_external_config(name):
"""
Load the external config file
"""
config = None
config_path = name
config_filename = os.path.join(os.path.dirname(sys.executable), config_path)
if os.path.exists(config_filename):
spec = importlib.util.spec_from_file_location(name, config_filename)
config = importlib.util.module_from_spec(spec)
spec.loader.exec_module(config)
else:
import config
return config
config_path = resource_path("user_config.py")
default_config_path = resource_path("config.py")
config = (
load_external_config("user_config.py")
if os.path.exists(config_path)
else load_external_config("config.py")
)
def setup_driver():
@ -101,7 +144,7 @@ def get_channel_items():
current_category = ""
pattern = r"^(.*?),(?!#genre#)(.*?)$"
with open(user_source_file, "r", encoding="utf-8") as f:
with open(resource_path(user_source_file), "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if "#genre#" in line:
@ -269,8 +312,10 @@ def update_file(final_file, old_file):
"""
Update the file
"""
if os.path.exists(old_file):
os.replace(old_file, final_file)
old_file_path = resource_path(old_file, persistent=True)
final_file_path = resource_path(final_file, persistent=True)
if os.path.exists(old_file_path):
os.replace(old_file_path, final_file_path)
def get_channel_url(element):
@ -423,7 +468,7 @@ def get_total_urls_from_info_list(infoList):
Get the total urls from info list
"""
total_urls = [url for url, _, _ in infoList]
return list(dict.fromkeys(total_urls))[: config.urls_limit]
return list(dict.fromkeys(total_urls))[: int(config.urls_limit)]
def get_total_urls_from_sorted_data(data):