From 27f6bc11c09b9025d2fb051d317326c5da6007a8 Mon Sep 17 00:00:00 2001 From: "guorong.zheng" <360996299@qq.com> Date: Wed, 21 Aug 2024 18:23:02 +0800 Subject: [PATCH] feat:source_channels --- README.md | 2 +- README_en.md | 2 +- config/config.ini | 6 ++-- docs/config.md | 5 +-- docs/config_en.md | 7 ++-- main.py | 3 +- tkinter_ui/default.py | 30 +++++++++++++++-- tkinter_ui/tkinter_ui.py | 72 ++++++++++++---------------------------- utils/channel.py | 29 +++++++++++++--- utils/config.py | 27 +++++++++++---- 10 files changed, 110 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index da522157fa..d0e25541f1 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ ## 特点 - 自定义模板,生成您想要的频道分类与频道顺序 -- 支持多种获取源方式:线上检索、组播源、酒店源、订阅源 +- 支持多种获取源方式:组播源、酒店源、订阅源、线上检索 - 接口测速验效,响应时间、分辨率优先级,过滤无效接口 - 定时执行,北京时间每日 6:00 与 18:00 执行更新 - 支持多种运行方式:工作流、命令行、界面软件、Docker diff --git a/README_en.md b/README_en.md index 7d2d27a147..417e846cfa 100644 --- a/README_en.md +++ b/README_en.md @@ -25,7 +25,7 @@ Customize channel menus and automatically obtain and update the latest live sour ## Features - Custom templates for creating desired channel categories and order -- Supports multiple source acquisition methods: online search, multicast source, hotel source, subscription source +- Supports multiple source acquisition methods: multicast source, hotel source, subscription source, online search - Interface speed testing and verification, with priority on response time and resolution, filtering out ineffective interfaces - Scheduled execution at 6:00 AM and 18:00 PM Beijing time daily - Supports various execution methods: workflows, command line, GUI software, Docker diff --git a/config/config.ini b/config/config.ini index c922a6e37b..5fb336959a 100644 --- a/config/config.ini +++ b/config/config.ini @@ -3,6 +3,7 @@ open_update = True open_use_old_result = True source_file = config/demo.txt final_file = output/result.txt +source_channels = 广东珠江,广东体育,广东新闻,广东卫视,大湾区卫视,江门综合,江门侨乡生活,新会综合,鹤山综合,佛山综合,佛山公共,佛山影视,深圳卫视,CCTV-1,CCTV-2,CCTV-3,CCTV-4,CCTV-5,CCTV-5+,CCTV-6,CCTV-7,CCTV-8,CCTV-9,CCTV-10,CCTV-11,CCTV-12,CCTV-13,CCTV-14,CCTV-15,CCTV-16,CCTV-17,广东卫视,浙江卫视,湖南卫视,北京卫视,湖北卫视,黑龙江卫视,安徽卫视,重庆卫视,东方卫视,东南卫视,甘肃卫视,广西卫视,贵州卫视,海南卫视,河北卫视,河南卫视,吉林卫视,江苏卫视,江西卫视,辽宁卫视,内蒙古卫视,宁夏卫视,青海卫视,山东卫视,山西卫视,陕西卫视,四川卫视,深圳卫视,三沙卫视,天津卫视,西藏卫视,新疆卫视,云南卫视,翡翠台,明珠台,星河台,凤凰中文,凤凰资讯,凤凰香港,TVBS亚洲,TVBS新闻,TVBS欢乐 open_online_search = False online_search_page_num = 5 @@ -23,12 +24,11 @@ subscribe_urls = https://m3u.ibert.me/txt/fmml_dv6.txt,https://m3u.ibert.me/txt/ open_multicast = True multicast_region_list = 广东 multicast_page_num = 5 - open_proxy = False open_driver = False - open_hotel = False open_hotel_tonkiang = True open_hotel_fofa = True hotel_region_list = 广东 -hotel_page_num = 5 \ No newline at end of file +hotel_page_num = 5 + diff --git a/docs/config.md b/docs/config.md index fd44ae9917..b88e122865 100644 --- a/docs/config.md +++ b/docs/config.md @@ -4,8 +4,9 @@ | open_use_old_result | True | 开启使用历史更新结果,合并至本次更新中 | | open_driver | False | 开启浏览器运行,若更新无数据可开启此模式,较消耗性能 | | open_proxy | True | 开启代理,自动获取免费可用代理,若更新无数据可开启此模式 | -| source_file | config/demo.txt | 模板文件名称 | -| final_file | output/result.txt | 生成文件名称 | +| source_file | config/demo.txt | 模板文件路径 | +| source_channels | | 获取更新的频道名称 | +| final_file | output/result.txt | 生成结果文件路径 | | open_online_search | False | 开启线上检索源功能 | | online_search_page_num | 5 | 在线检索频道获取分页数量 | | urls_limit | 15 | 单个频道接口数量 | diff --git a/docs/config_en.md b/docs/config_en.md index 0cd553f868..22c214c04c 100644 --- a/docs/config_en.md +++ b/docs/config_en.md @@ -4,10 +4,11 @@ | open_use_old_result | True | Enable the use of historical update results and merge them into the current update | | open_driver | False | Enable browser execution, If there are no updates, this mode can be enabled, which consumes more performance | | open_proxy | True | Enable proxy, automatically obtains free available proxies, If there are no updates, this mode can be enabled | -| source_file | config/demo.txt | Template file name | -| final_file | output/result.txt | Generated file name | +| source_file | config/demo.txt | Template file path | +| source_channels | | The names of the channel to be updated | +| final_file | output/result.txt | Generated result file path | | open_online_search | False | Enable online search source feature | -| page_num | 5 | Page retrieval quantity for channels | +| online_search_page_num | 5 | Page retrieval quantity for online search channels | | urls_limit | 10 | Number of interfaces per channel | | open_keep_all | False | Retain all search results, retain results with non-template channel names, recommended to be turned on when manually maintaining | | open_sort | True | Enable the sorting function (response speed, date, resolution) | diff --git a/main.py b/main.py index 9f47683f3c..c77e0d7db0 100644 --- a/main.py +++ b/main.py @@ -42,7 +42,7 @@ class UpdateSource: def __init__(self): self.run_ui = False self.tasks = [] - self.channel_items = get_channel_items() + self.channel_items = {} self.subscribe_result = {} self.multicast_result = {} self.hotel_tonkiang_result = {} @@ -93,6 +93,7 @@ def sort_pbar_update(self): async def main(self): try: + self.channel_items = get_channel_items() channel_names = [ name for channel_obj in self.channel_items.values() diff --git a/tkinter_ui/default.py b/tkinter_ui/default.py index 3dde149055..56324f9898 100644 --- a/tkinter_ui/default.py +++ b/tkinter_ui/default.py @@ -74,6 +74,24 @@ def init_ui(self, root): ) self.source_file_button.pack(side=tk.LEFT, padx=4, pady=0) + frame_default_source_channels = tk.Frame(root) + frame_default_source_channels.pack(fill=tk.X) + + self.source_channels_label = tk.Label( + frame_default_source_channels, text="频道名称:", width=8 + ) + self.source_channels_label.pack(side=tk.LEFT, padx=4, pady=8) + self.source_channels_text = scrolledtext.ScrolledText( + frame_default_source_channels, height=5 + ) + self.source_channels_text.pack( + side=tk.LEFT, padx=4, pady=8, expand=True, fill=tk.BOTH + ) + self.source_channels_text.insert( + tk.END, config.get("Settings", "source_channels") + ) + self.source_channels_text.bind("", self.update_source_channels) + frame_default_final_file = tk.Frame(root) frame_default_final_file.pack(fill=tk.X) @@ -241,7 +259,7 @@ def init_ui(self, root): ) self.domain_blacklist_label.pack(side=tk.LEFT, padx=4, pady=8) self.domain_blacklist_text = scrolledtext.ScrolledText( - frame_default_domain_blacklist, height=5 + frame_default_domain_blacklist, height=2 ) self.domain_blacklist_text.pack( side=tk.LEFT, padx=4, pady=8, expand=True, fill=tk.BOTH @@ -259,7 +277,7 @@ def init_ui(self, root): ) self.url_keywords_blacklist_label.pack(side=tk.LEFT, padx=4, pady=8) self.url_keywords_blacklist_text = scrolledtext.ScrolledText( - frame_default_url_keywords_blacklist, height=5 + frame_default_url_keywords_blacklist, height=2 ) self.url_keywords_blacklist_text.pack( side=tk.LEFT, padx=4, pady=8, expand=True, fill=tk.BOTH @@ -288,6 +306,13 @@ def select_source_file(self): self.source_file_entry.insert(0, filepath) config.set("Settings", "source_file", filepath) + def update_source_channels(self, event): + config.set( + "Settings", + "source_channels", + self.source_channels_text.get(1.0, tk.END), + ) + def select_final_file(self): filepath = filedialog.askopenfilename( initialdir=os.getcwd(), title="选择结果文件", filetypes=[("txt", "*.txt")] @@ -352,6 +377,7 @@ def change_entry_state(self, state): "open_proxy_checkbutton", "source_file_entry", "source_file_button", + "source_channels_text", "final_file_entry", "final_file_button", "open_keep_all_checkbutton", diff --git a/tkinter_ui/tkinter_ui.py b/tkinter_ui/tkinter_ui.py index 0d4460f817..019827e5d6 100644 --- a/tkinter_ui/tkinter_ui.py +++ b/tkinter_ui/tkinter_ui.py @@ -5,7 +5,7 @@ import tkinter as tk from tkinter import messagebox from tkinter import ttk -from utils.config import config, resource_path +from utils.config import config, resource_path, save_config from main import UpdateSource import asyncio import threading @@ -32,31 +32,6 @@ def __init__(self, root): self.online_search_ui = OnlineSearchUI() self.update_source = UpdateSource() self.update_running = False - self.config_entrys = [ - "open_update_checkbutton", - "open_use_old_result_checkbutton", - "open_driver_checkbutton", - "open_proxy_checkbutton", - "source_file_entry", - "source_file_button", - "final_file_entry", - "final_file_button", - "open_subscribe_checkbutton", - "open_multicast_checkbutton", - "open_online_search_checkbutton", - "open_keep_all_checkbutton", - "open_sort_checkbutton", - "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_combo", - ] self.result_url = None def view_result_link_callback(self, event): @@ -67,6 +42,7 @@ def save_config(self): "open_update": self.default_ui.open_update_var.get(), "open_use_old_result": self.default_ui.open_use_old_result_var.get(), "source_file": self.default_ui.source_file_entry.get(), + "source_channels": self.default_ui.source_channels_text.get(1.0, tk.END), "final_file": self.default_ui.final_file_entry.get(), "urls_limit": self.default_ui.urls_limit_entry.get(), "open_driver": self.default_ui.open_driver_var.get(), @@ -83,34 +59,35 @@ def save_config(self): "open_subscribe": self.subscribe_ui.open_subscribe_var.get(), "subscribe_urls": self.subscribe_ui.subscribe_urls_text.get(1.0, tk.END), "open_multicast": self.multicast_ui.open_multicast_var.get(), - "region_list": self.multicast_ui.region_list_combo.get(), + "multicast_region_list": self.multicast_ui.region_list_combo.get(), + "multicast_page_num": self.multicast_ui.page_num_entry.get(), + "open_hotel": self.hotel_ui.open_hotel_var.get(), + "open_hotel_tonkiang": self.hotel_ui.open_hotel_tonkiang_var.get(), + "open_hotel_fofa": self.hotel_ui.open_hotel_fofa_var.get(), + "hotel_region_list": self.hotel_ui.region_list_combo.get(), + "hotel_page_num": self.hotel_ui.page_num_entry.get(), "open_online_search": self.online_search_ui.open_online_search_var.get(), - "page_num": self.online_search_ui.page_num_entry.get(), + "online_search_page_num": self.online_search_ui.page_num_entry.get(), "recent_days": self.online_search_ui.recent_days_entry.get(), } for key, value in config_values.items(): config.set("Settings", key, str(value)) - user_config_file = "config/" + ( - "user_config.ini" - if os.path.exists(resource_path("user_config.ini")) - else "config.ini" - ) - user_config_path = resource_path(user_config_file, persistent=True) - os.makedirs(os.path.dirname(user_config_path), exist_ok=True) - with open(user_config_path, "w", encoding="utf-8") as configfile: - config.write(configfile) + save_config() messagebox.showinfo("提示", "保存成功") + def change_state(self, state): + self.default_ui.change_entry_state(state=state) + self.multicast_ui.change_entry_state(state=state) + self.hotel_ui.change_entry_state(state=state) + self.subscribe_ui.change_entry_state(state=state) + self.online_search_ui.change_entry_state(state=state) + async def run_update(self): self.update_running = not self.update_running if self.update_running: self.run_button.config(text="取消更新", state="normal") - self.default_ui.change_entry_state(state="disabled") - self.multicast_ui.change_entry_state(state="disabled") - self.hotel_ui.change_entry_state(state="disabled") - self.subscribe_ui.change_entry_state(state="disabled") - self.online_search_ui.change_entry_state(state="disabled") + self.change_state("disabled") self.progress_bar["value"] = 0 self.progress_label.pack() self.view_result_link.pack() @@ -120,11 +97,7 @@ async def run_update(self): self.stop() self.update_source.stop() self.run_button.config(text="开始更新", state="normal") - self.default_ui.change_entry_state(state="normal") - self.multicast_ui.change_entry_state(state="normal") - self.hotel_ui.change_entry_state(state="normal") - self.subscribe_ui.change_entry_state(state="normal") - self.online_search_ui.change_entry_state(state="normal") + self.change_state("normal") self.progress_bar.pack_forget() self.view_result_link.pack_forget() self.progress_label.pack_forget() @@ -149,8 +122,7 @@ def update_progress(self, title, progress, finished=False, url=None): 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") + self.change_state("normal") if url: self.view_result_link.config(text=url) self.result_url = url @@ -246,7 +218,7 @@ def init_UI(self): screen_width = root.winfo_screenwidth() screen_height = root.winfo_screenheight() width = 550 - height = 700 + height = 750 x = (screen_width / 2) - (width / 2) y = (screen_height / 2) - (height / 2) root.geometry("%dx%d+%d+%d" % (width, height, x, y)) diff --git a/utils/channel.py b/utils/channel.py index 67b4c65828..966ac00775 100644 --- a/utils/channel.py +++ b/utils/channel.py @@ -1,4 +1,4 @@ -from utils.config import config, resource_path +from utils.config import config, resource_path, save_config from utils.tools import check_url_by_patterns, get_total_urls_from_info_list from utils.speed import sort_urls_by_speed_and_resolution import os @@ -24,7 +24,7 @@ ) -def get_channel_data_from_file(channels, file, from_result=False): +def get_channel_data_from_file(channels=None, file=None, names=None, from_result=False): """ Get the channel data from the file """ @@ -43,6 +43,8 @@ def get_channel_data_from_file(channels, file, from_result=False): match = re.search(pattern, line) if match is not None: name = match.group(1).strip() + if name not in names: + continue url = match.group(2).strip() if url and url not in channels[current_category][name]: channels[current_category][name].append(url) @@ -56,17 +58,36 @@ def get_channel_items(): user_source_file = config.get("Settings", "source_file") user_final_file = config.get("Settings", "final_file") channels = defaultdict(lambda: defaultdict(list)) + source_channel_names = config.get("Settings", "source_channels").split(",") if os.path.exists(resource_path(user_source_file)): with open(resource_path(user_source_file), "r", encoding="utf-8") as file: - channels = get_channel_data_from_file(channels, file) + channels = get_channel_data_from_file( + channels=channels, file=file, names=source_channel_names + ) if config.getboolean("Settings", "open_use_old_result") and os.path.exists( resource_path(user_final_file) ): with open(resource_path(user_final_file), "r", encoding="utf-8") as file: - channels = get_channel_data_from_file(channels, file, from_result=True) + channels = get_channel_data_from_file( + channels=channels, + file=file, + names=source_channel_names, + from_result=True, + ) + channel_names = [ + name for channel_obj in channels.values() for name in channel_obj.keys() + ] + for source_name in source_channel_names: + if source_name not in channel_names: + channels["自定义频道"][source_name] = [] + total_channel_names = ",".join( + [name for channel_obj in channels.values() for name in channel_obj.keys()] + ) + config.set("Settings", "source_channels", total_channel_names) + save_config() return channels diff --git a/utils/config.py b/utils/config.py index d68b529870..8f9f4bd7d8 100644 --- a/utils/config.py +++ b/utils/config.py @@ -1,4 +1,4 @@ -from os import path +import os import sys import configparser @@ -7,14 +7,14 @@ def resource_path(relative_path, persistent=False): """ Get the resource path """ - base_path = path.abspath(".") - total_path = path.join(base_path, relative_path) - if persistent or path.exists(total_path): + base_path = os.path.abspath(".") + total_path = os.path.join(base_path, relative_path) + if persistent or os.path.exists(total_path): return total_path else: try: base_path = sys._MEIPASS - return path.join(base_path, relative_path) + return os.path.join(base_path, relative_path) except Exception: return total_path @@ -29,7 +29,7 @@ def get_config(): config_files = [user_config_path, default_config_path] for config_file in config_files: - if path.exists(config_file): + if os.path.exists(config_file): with open(config_file, "r", encoding="utf-8") as f: config_parser.read_file(f) break @@ -38,3 +38,18 @@ def get_config(): config = get_config() + + +def save_config(): + """ + Save config with write + """ + user_config_file = "config/" + ( + "user_config.ini" + if os.path.exists(resource_path("user_config.ini")) + else "config.ini" + ) + user_config_path = resource_path(user_config_file, persistent=True) + os.makedirs(os.path.dirname(user_config_path), exist_ok=True) + with open(user_config_path, "w", encoding="utf-8") as configfile: + config.write(configfile)