From 4136e522174c812321f197eebbd359faf1605bfd Mon Sep 17 00:00:00 2001 From: JGZ_YES Date: Tue, 7 Apr 2026 21:55:15 +0800 Subject: [PATCH] Add files via upload --- scd.py | 444 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 444 insertions(+) create mode 100644 scd.py diff --git a/scd.py b/scd.py new file mode 100644 index 0000000..ab5e0c3 --- /dev/null +++ b/scd.py @@ -0,0 +1,444 @@ +#!/usr/bin/env python3 +""" +SCL 插件下载器 (scd) +用于下载和更新 SCL 插件的工具 +""" + +import os +import sys +import json +import time +import argparse +import threading +import concurrent.futures +import urllib.request +import urllib.error +import platform +import subprocess +from datetime import datetime + +# 版本信息 +VERSION = "1.0.0" + +# 默认镜像源 +DEFAULT_MIRROR = "https://scl.ecuil.com/run/plugins/" + +# 配置文件路径 +CONFIG_FILE = os.path.join(os.path.expanduser("~"), ".scd_config.json") + +class SCDownloader: + def __init__(self): + self.config = self.load_config() + self.mirrors = self.config.get("mirrors", [DEFAULT_MIRROR]) + self.current_mirror = self.config.get("current_mirror", DEFAULT_MIRROR) + # 获取 CPU 核心数,用于多线程下载 + self.cpu_cores = os.cpu_count() or 4 + self.threads = max(1, self.cpu_cores // 2) + + def load_config(self): + """加载配置文件""" + if os.path.exists(CONFIG_FILE): + try: + with open(CONFIG_FILE, "r", encoding="utf-8") as f: + return json.load(f) + except Exception as e: + print(f"加载配置文件失败: {e}") + return {"mirrors": [DEFAULT_MIRROR], "current_mirror": DEFAULT_MIRROR} + + def save_config(self): + """保存配置文件""" + self.config["mirrors"] = self.mirrors + self.config["current_mirror"] = self.current_mirror + try: + os.makedirs(os.path.dirname(CONFIG_FILE), exist_ok=True) + with open(CONFIG_FILE, "w", encoding="utf-8") as f: + json.dump(self.config, f, indent=2) + except Exception as e: + print(f"保存配置文件失败: {e}") + + def add_mirror(self, mirror_url): + """添加镜像源""" + if mirror_url not in self.mirrors: + self.mirrors.append(mirror_url) + self.current_mirror = mirror_url + self.save_config() + print(f"镜像 {mirror_url} 已添加并设为当前镜像") + else: + print(f"镜像 {mirror_url} 已存在") + + def download_file(self, url, output_path, show_progress=True): + """下载文件并显示进度(模范 PIP 风格)""" + try: + import requests + from tqdm import tqdm + + # 发送 HEAD 请求获取文件大小 + response = requests.head(url) + total_size = int(response.headers.get('content-length', 0)) + + # 下载文件并显示进度条 + with requests.get(url, stream=True) as r: + r.raise_for_status() + with open(output_path, 'wb') as f: + with tqdm( + total=total_size, + unit='B', + unit_scale=True, + unit_divisor=1024, + desc=os.path.basename(output_path), + bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt} [{rate_fmt}]' + ) as pbar: + for chunk in r.iter_content(chunk_size=8192): + if chunk: + f.write(chunk) + pbar.update(len(chunk)) + return True + except ImportError: + # 如果没有 requests 和 tqdm,使用 urllib 作为后备 + print("警告: 使用后备下载方法。安装 requests 和 tqdm 以获得更好的进度条。") + + def progress_hook(count, block_size, total_size): + if total_size > 0: + percent = int(count * block_size * 100 / total_size) + if show_progress: + # 动画效果:旋转指示器 + 进度条 + spinner = "|/-\\" + spinner_pos = int(time.time() * 10) % 4 + bar_length = 40 + filled_length = int(bar_length * percent / 100) + bar = "█" * filled_length + "-" * (bar_length - filled_length) + sys.stdout.write(f"\r{spinner[spinner_pos]} 下载中: [{bar}] {percent}% ") + sys.stdout.flush() + + # 使用 urllib 下载文件 + urllib.request.urlretrieve(url, output_path, reporthook=progress_hook) + if show_progress: + sys.stdout.write("\r" + " " * 80 + "\r") + sys.stdout.flush() + return True + except urllib.error.URLError as e: + print(f"\n下载文件失败: {e}") + return False + except Exception as e: + print(f"\n错误: {e}") + return False + + def install_plugin(self, plugin_name): + """安装插件""" + print(f"正在安装插件: {plugin_name}") + + # 构建插件下载 URL + plugin_url = f"{self.current_mirror}{plugin_name}.py" + plugin_path = os.path.join("plugins", f"{plugin_name}.py") + + # 确保 plugins 目录存在 + os.makedirs("plugins", exist_ok=True) + + # 下载插件 + print(f"从以下地址下载: {plugin_url}") + if self.download_file(plugin_url, plugin_path): + print(f"插件 {plugin_name} 安装成功!") + return True + else: + print(f"安装插件 {plugin_name} 失败") + return False + + def install_from_file(self, file_path): + """从文件安装多个插件""" + print(f"从文件安装插件: {file_path}") + + if not os.path.exists(file_path): + print(f"文件 {file_path} 不存在") + return False + + try: + with open(file_path, "r", encoding="utf-8") as f: + plugins = [line.strip() for line in f if line.strip() and not line.strip().startswith("#")] + + if not plugins: + print("文件中未找到插件") + return False + + # 使用线程池下载插件 + with concurrent.futures.ThreadPoolExecutor(max_workers=self.threads) as executor: + future_to_plugin = {executor.submit(self.install_plugin, plugin): plugin for plugin in plugins} + + for future in concurrent.futures.as_completed(future_to_plugin): + plugin = future_to_plugin[future] + try: + future.result() + except Exception as e: + print(f"安装 {plugin} 时出错: {e}") + + print("所有插件安装完成!") + return True + except Exception as e: + print(f"读取文件失败: {e}") + return False + + def get_plugin_version(self, plugin_name): + """获取插件版本信息""" + # 尝试从本地插件文件中提取版本信息 + plugin_path = os.path.join("plugins", f"{plugin_name}.py") + if os.path.exists(plugin_path): + try: + with open(plugin_path, "r", encoding="utf-8") as f: + content = f.read() + # 查找版本信息 + import re + version_match = re.search(r'VERSION\s*=\s*["\'](.*?)["\']', content) + if version_match: + return version_match.group(1) + except Exception as e: + pass + return "0.0.0" + + def get_remote_plugin_version(self, plugin_name): + """获取远程插件版本信息""" + try: + import requests + version_url = f"{self.current_mirror}{plugin_name}.json" + response = requests.get(version_url) + response.raise_for_status() + version_data = response.json() + return version_data.get("version", "0.0.0") + except Exception as e: + print(f"获取远程版本信息失败: {e}") + return "0.0.0" + + def compare_versions(self, v1, v2): + """比较版本号""" + def normalize_version(v): + return [int(x) for x in v.split(".") if x.isdigit()] + + v1_parts = normalize_version(v1) + v2_parts = normalize_version(v2) + + for i in range(max(len(v1_parts), len(v2_parts))): + v1_part = v1_parts[i] if i < len(v1_parts) else 0 + v2_part = v2_parts[i] if i < len(v2_parts) else 0 + + if v1_part < v2_part: + return -1 + elif v1_part > v2_part: + return 1 + return 0 + + def get_scd_version(self): + """获取 scd 版本信息""" + # 尝试从当前文件中提取版本信息 + scd_path = os.path.abspath(__file__) + try: + with open(scd_path, "r", encoding="utf-8") as f: + content = f.read() + # 查找版本信息 + import re + version_match = re.search(r'VERSION\s*=\s*["\'](.*?)["\']', content) + if version_match: + return version_match.group(1) + except Exception as e: + pass + return "0.0.0" + + def get_remote_scd_version(self): + """获取远程 scd 版本信息""" + try: + import requests + version_url = "https://scl.ecuil.com/scd.json" + response = requests.get(version_url) + response.raise_for_status() + version_data = response.json() + return version_data.get("v", "0.0.0") + except Exception as e: + print(f"获取远程版本信息失败: {e}") + return "0.0.0" + + def update_plugin(self, plugin_name): + """更新插件""" + if plugin_name == "scd": + # 更新 scd 本身 + print("正在更新 scd...") + + # 获取本地版本 + local_version = self.get_scd_version() + print(f"本地版本: {local_version}") + + # 获取远程版本 + remote_version = self.get_remote_scd_version() + print(f"远程版本: {remote_version}") + + # 比较版本 + if self.compare_versions(local_version, remote_version) < 0: + print("发现新版本,正在下载...") + + # 根据操作系统类型选择下载文件 + import platform + system = platform.system() + + if system == "Windows": + scd_url = "https://scl.ecuil.com/scd.exe" + scd_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "scd.exe") + elif system == "Linux": + scd_url = "https://scl.ecuil.com/scd" + scd_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "scd") + else: + print("不支持的操作系统") + return False + + # 下载更新 + if self.download_file(scd_url, scd_path): + # 如果是 Linux,设置可执行权限 + if system == "Linux": + try: + import os + os.chmod(scd_path, 0o755) + except Exception as e: + print(f"设置可执行权限失败: {e}") + + print("scd 更新成功!") + print(f"请运行新的 scd{'文件' if system == 'Windows' else '可执行文件'}") + return True + else: + print("下载更新失败") + return False + else: + print("scd 已是最新版本,无需更新") + return True + else: + # 更新普通插件 + print(f"正在更新插件: {plugin_name}") + + # 获取本地版本 + local_version = self.get_plugin_version(plugin_name) + print(f"本地版本: {local_version}") + + # 获取远程版本 + remote_version = self.get_remote_plugin_version(plugin_name) + print(f"远程版本: {remote_version}") + + # 比较版本 + if self.compare_versions(local_version, remote_version) < 0: + print("发现新版本,正在下载...") + return self.install_plugin(plugin_name) + else: + print("插件已是最新版本,无需更新") + return True + + def list_plugins(self, mode="local"): + """列出可用插件""" + if mode == "local": + print("正在列出本地插件...") + plugins_dir = "plugins" + if os.path.exists(plugins_dir): + plugins = [f[:-3] for f in os.listdir(plugins_dir) if f.endswith(".py")] + if plugins: + print("已安装的插件:") + for plugin in plugins: + print(f" - {plugin}") + else: + print("没有安装插件") + else: + print("未找到插件目录") + elif mode == "web": + print("正在列出网络插件...") + print(f"从以下地址获取插件: {self.current_mirror}") + try: + # 尝试从 API 获取插件列表 + import requests + plugins_url = f"{self.current_mirror}plugins.php" + response = requests.get(plugins_url) + response.raise_for_status() + plugins_data = response.json() + + if plugins_data.get("status") == "success" and "plugins" in plugins_data: + plugins = plugins_data["plugins"] + if plugins: + print("可用的插件:") + for plugin in plugins: + print(f" - {plugin}") + else: + print("没有可用的插件") + else: + print("API 响应无效") + except ImportError: + print("错误: 请安装必要的包: pip install requests") + except Exception as e: + print(f"获取插件列表失败: {e}") + print("请检查网络连接或镜像 URL") + + def show_mirrors(self): + """显示可用镜像源""" + print("可用的镜像源:") + for i, mirror in enumerate(self.mirrors): + marker = "*" if mirror == self.current_mirror else " " + print(f"{marker} [{i+1}] {mirror}") + + def run(self, args): + """运行命令""" + if args.command == "install": + if args.file: + self.install_from_file(args.file) + else: + for plugin in args.plugins: + self.install_plugin(plugin) + elif args.command == "update": + for plugin in args.plugins: + self.update_plugin(plugin) + elif args.command == "mirror": + if args.url: + self.add_mirror(args.url) + else: + self.show_mirrors() + elif args.command == "list": + if hasattr(args, 'mode') and args.mode: + self.list_plugins(args.mode) + else: + self.list_plugins() + elif args.command == "info": + print("SCL 插件下载器 (scd)") + print(f"版本: 1.0.0") + print(f"当前镜像: {self.current_mirror}") + print(f"可用镜像: {len(self.mirrors)}") + print(f"CPU 核心数: {self.cpu_cores}") + print(f"下载线程数: {self.threads}") + +def main(): + parser = argparse.ArgumentParser(description="SCL 插件下载器") + subparsers = parser.add_subparsers(dest="command", help="可用命令") + + # install 命令 + install_parser = subparsers.add_parser("install", help="安装插件") + install_parser.add_argument("plugins", nargs="*", help="要安装的插件名称") + install_parser.add_argument("-f", "--file", help="包含插件名称的文件") + install_parser.add_argument("-t", action="store_true", help="从 plugins.txt 安装") + + # update 命令 + update_parser = subparsers.add_parser("update", help="更新插件") + update_parser.add_argument("plugins", nargs="*", help="要更新的插件名称") + + # mirror 命令 + mirror_parser = subparsers.add_parser("mirror", help="管理镜像源") + mirror_parser.add_argument("url", nargs="?", help="要添加的镜像 URL") + + # list 命令 + list_parser = subparsers.add_parser("list", help="列出插件") + list_parser.add_argument("mode", nargs="?", choices=["local", "web"], default="local", help="列出模式: local 或 web") + + # info 命令 + subparsers.add_parser("info", help="显示 scd 信息") + + args = parser.parse_args() + + # 处理 -t 参数 + if args.command == "install" and args.t: + args.file = "plugins.txt" + + if not args.command: + parser.print_help() + return + + downloader = SCDownloader() + downloader.run(args) + +if __name__ == "__main__": + main()