Add files via upload

这个提交包含在:
2026-04-07 21:55:15 +08:00
提交者 GitHub
父节点 42d30b2542
当前提交 4136e52217
+444
查看文件
@@ -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()