ErisPulse 1.1.10__py3-none-any.whl → 1.1.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ErisPulse/__init__.py +9 -3
- ErisPulse/__main__.py +600 -198
- ErisPulse/adapter.py +29 -13
- ErisPulse/db.py +5 -2
- ErisPulse/mods.py +5 -0
- ErisPulse/util.py +28 -2
- {erispulse-1.1.10.dist-info → erispulse-1.1.12.dist-info}/METADATA +13 -3
- erispulse-1.1.12.dist-info/RECORD +14 -0
- erispulse-1.1.10.dist-info/RECORD +0 -14
- {erispulse-1.1.10.dist-info → erispulse-1.1.12.dist-info}/WHEEL +0 -0
- {erispulse-1.1.10.dist-info → erispulse-1.1.12.dist-info}/entry_points.txt +0 -0
- {erispulse-1.1.10.dist-info → erispulse-1.1.12.dist-info}/licenses/LICENSE +0 -0
- {erispulse-1.1.10.dist-info → erispulse-1.1.12.dist-info}/top_level.txt +0 -0
ErisPulse/__main__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import os
|
|
3
3
|
import sys
|
|
4
|
+
import time
|
|
4
5
|
import shutil
|
|
5
6
|
import aiohttp
|
|
6
7
|
import zipfile
|
|
@@ -10,53 +11,166 @@ import subprocess
|
|
|
10
11
|
import json
|
|
11
12
|
from .db import env
|
|
12
13
|
from .mods import mods
|
|
14
|
+
from watchdog.observers import Observer
|
|
15
|
+
from watchdog.events import FileSystemEventHandler
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
17
|
+
class Shell_Printer:
|
|
18
|
+
# ANSI 颜色代码
|
|
19
|
+
RESET = "\033[0m"
|
|
20
|
+
BOLD = "\033[1m"
|
|
21
|
+
RED = "\033[91m"
|
|
22
|
+
GREEN = "\033[92m"
|
|
23
|
+
YELLOW = "\033[93m"
|
|
24
|
+
BLUE = "\033[94m"
|
|
25
|
+
MAGENTA = "\033[95m"
|
|
26
|
+
CYAN = "\033[96m"
|
|
27
|
+
WHITE = "\033[97m"
|
|
28
|
+
DIM = "\033[2m"
|
|
29
|
+
UNDERLINE = "\033[4m"
|
|
30
|
+
BG_BLUE = "\033[44m"
|
|
31
|
+
BG_GREEN = "\033[42m"
|
|
32
|
+
BG_YELLOW = "\033[43m"
|
|
33
|
+
BG_RED = "\033[41m"
|
|
34
|
+
|
|
35
|
+
def __init__(self):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def _get_color(cls, level):
|
|
40
|
+
"""根据消息级别返回颜色"""
|
|
41
|
+
return {
|
|
42
|
+
"info": cls.CYAN,
|
|
43
|
+
"success": cls.GREEN,
|
|
44
|
+
"warning": cls.YELLOW,
|
|
45
|
+
"error": cls.RED,
|
|
46
|
+
"title": cls.MAGENTA,
|
|
47
|
+
"default": cls.RESET,
|
|
48
|
+
}.get(level, cls.RESET)
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def panel(cls, msg: str, title: str = None, level: str = "info") -> None:
|
|
52
|
+
"""带标题和边框的面板,支持颜色编码"""
|
|
53
|
+
color = cls._get_color(level)
|
|
54
|
+
border_char = "═" * 60
|
|
55
|
+
|
|
56
|
+
# 标题行
|
|
57
|
+
title_line = ""
|
|
58
|
+
if title:
|
|
59
|
+
title = f" {title.upper()} "
|
|
60
|
+
title_padding = (60 - len(title) - 4) // 2
|
|
61
|
+
left_pad = " " * title_padding
|
|
62
|
+
right_pad = " " * (60 - len(title) - title_padding - 4)
|
|
63
|
+
title_line = f"{cls.DIM}╔{left_pad}{cls.BOLD}{color}{title}{cls.RESET}{cls.DIM}{right_pad}╗{cls.RESET}\n"
|
|
64
|
+
|
|
65
|
+
# 内容行
|
|
66
|
+
lines = []
|
|
67
|
+
for line in msg.split("\n"):
|
|
68
|
+
padding = (60 - len(line) - 4) // 2
|
|
69
|
+
left_pad = " " * padding
|
|
70
|
+
right_pad = " " * (60 - len(line) - padding - 4)
|
|
71
|
+
lines.append(f"{cls.DIM}║{cls.RESET}{left_pad}{line}{right_pad}{cls.DIM}║{cls.RESET}")
|
|
72
|
+
|
|
73
|
+
# 底部边框
|
|
74
|
+
bottom_border = f"{cls.DIM}╚{border_char}╝{cls.RESET}"
|
|
75
|
+
|
|
76
|
+
# 组合所有部分
|
|
77
|
+
panel = f"{title_line}{border_char}\n"
|
|
78
|
+
panel += "\n".join(lines) + "\n"
|
|
79
|
+
panel += f"{border_char}\n{bottom_border}\n"
|
|
80
|
+
|
|
81
|
+
print(panel)
|
|
46
82
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
83
|
+
@classmethod
|
|
84
|
+
def table(cls, headers, rows, title=None, level="info") -> None:
|
|
85
|
+
"""改进的表格输出,带有颜色和分隔线"""
|
|
86
|
+
color = cls._get_color(level)
|
|
87
|
+
if title:
|
|
88
|
+
print(f"{cls.BOLD}{color}== {title} =={cls.RESET}")
|
|
89
|
+
|
|
90
|
+
# 计算列宽
|
|
91
|
+
col_widths = [len(h) for h in headers]
|
|
92
|
+
for row in rows:
|
|
93
|
+
for i, cell in enumerate(row):
|
|
94
|
+
col_widths[i] = max(col_widths[i], len(str(cell)))
|
|
95
|
+
|
|
96
|
+
# 构建标题格式
|
|
97
|
+
fmt = "│".join(f" {{:<{w}}} " for w in col_widths)
|
|
98
|
+
|
|
99
|
+
# 顶部边框
|
|
100
|
+
top_border = "┌" + "┬".join("─" * (w+2) for w in col_widths) + "┐"
|
|
101
|
+
print(f"{cls.DIM}{top_border}{cls.RESET}")
|
|
102
|
+
|
|
103
|
+
# 表头
|
|
104
|
+
header_line = fmt.format(*headers)
|
|
105
|
+
print(f"{cls.BOLD}{color}│{header_line}│{cls.RESET}")
|
|
106
|
+
|
|
107
|
+
# 表头分隔线
|
|
108
|
+
separator = "├" + "┼".join("─" * (w+2) for w in col_widths) + "┤"
|
|
109
|
+
print(f"{cls.DIM}{separator}{cls.RESET}")
|
|
110
|
+
|
|
111
|
+
# 表格内容
|
|
112
|
+
for row in rows:
|
|
113
|
+
row_line = fmt.format(*row)
|
|
114
|
+
print(f"│{row_line}│")
|
|
115
|
+
|
|
116
|
+
# 底部边框
|
|
117
|
+
bottom_border = "└" + "┴".join("─" * (w+2) for w in col_widths) + "┘"
|
|
118
|
+
print(f"{cls.DIM}{bottom_border}{cls.RESET}")
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def progress_bar(cls, current, total, prefix="", suffix="", length=50):
|
|
122
|
+
"""显示进度条"""
|
|
123
|
+
filled_length = int(length * current // total)
|
|
124
|
+
percent = min(100.0, 100 * (current / float(total)))
|
|
125
|
+
bar = f"{cls.GREEN}{'█' * filled_length}{cls.WHITE}{'░' * (length - filled_length)}{cls.RESET}"
|
|
126
|
+
sys.stdout.write(f"\r{cls.BOLD}{prefix}{cls.RESET} {bar} {cls.BOLD}{percent:.1f}%{cls.RESET} {suffix}")
|
|
127
|
+
sys.stdout.flush()
|
|
128
|
+
if current == total:
|
|
129
|
+
print()
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def confirm(cls, msg, default=False) -> bool:
|
|
133
|
+
"""带颜色和默认选择的确认对话框"""
|
|
134
|
+
yes_options = {'y', 'yes'}
|
|
135
|
+
no_options = {'n', 'no'}
|
|
136
|
+
default_str = "Y/n" if default else "y/N"
|
|
137
|
+
prompt = f"{cls.BOLD}{msg}{cls.RESET} [{cls.CYAN}{default_str}{cls.RESET}]: "
|
|
138
|
+
|
|
139
|
+
while True:
|
|
140
|
+
ans = input(prompt).strip().lower()
|
|
141
|
+
if not ans:
|
|
142
|
+
return default
|
|
143
|
+
if ans in yes_options:
|
|
144
|
+
return True
|
|
145
|
+
if ans in no_options:
|
|
146
|
+
return False
|
|
147
|
+
print(f"{cls.YELLOW}请输入 'y' 或 'n'{cls.RESET}")
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def ask(cls, msg, choices=None, default=None) -> str | None:
|
|
151
|
+
"""带颜色和选择的提问"""
|
|
152
|
+
prompt = f"{cls.BOLD}{msg}{cls.RESET}"
|
|
153
|
+
if choices:
|
|
154
|
+
prompt += f" ({cls.CYAN}{'/'.join(choices)}{cls.RESET})"
|
|
155
|
+
if default:
|
|
156
|
+
prompt += f" [{cls.BLUE}默认: {default}{cls.RESET}]"
|
|
157
|
+
prompt += ": "
|
|
158
|
+
|
|
159
|
+
while True:
|
|
160
|
+
ans = input(prompt).strip()
|
|
161
|
+
if not ans and default:
|
|
162
|
+
return default
|
|
163
|
+
if not choices or ans in choices:
|
|
164
|
+
return ans
|
|
165
|
+
print(f"{cls.YELLOW}请输入有效选项: {', '.join(choices)}{cls.RESET}")
|
|
166
|
+
|
|
167
|
+
@classmethod
|
|
168
|
+
def status(cls, msg, success=True):
|
|
169
|
+
"""显示状态指示器"""
|
|
170
|
+
symbol = f"{cls.GREEN}✓" if success else f"{cls.RED}✗"
|
|
171
|
+
print(f"\r{symbol}{cls.RESET} {msg}")
|
|
172
|
+
|
|
173
|
+
shellprint = Shell_Printer()
|
|
60
174
|
|
|
61
175
|
class SourceManager:
|
|
62
176
|
def __init__(self):
|
|
@@ -68,7 +182,7 @@ class SourceManager:
|
|
|
68
182
|
|
|
69
183
|
async def _validate_url(self, url):
|
|
70
184
|
if not url.startswith(('http://', 'https://')):
|
|
71
|
-
protocol =
|
|
185
|
+
protocol = shellprint.confirm("未指定协议,请输入使用的协议", choices=['http', 'https'], default="https")
|
|
72
186
|
url = f"{protocol}://{url}"
|
|
73
187
|
if not url.endswith('.json'):
|
|
74
188
|
url = f"{url}/map.json"
|
|
@@ -79,38 +193,41 @@ class SourceManager:
|
|
|
79
193
|
if response.headers.get('Content-Type', '').startswith('application/json'):
|
|
80
194
|
return url
|
|
81
195
|
else:
|
|
82
|
-
|
|
196
|
+
shellprint.panel(f"源 {url} 返回的内容不是有效的 JSON 格式", "错误", "error")
|
|
83
197
|
return None
|
|
84
198
|
except Exception as e:
|
|
85
|
-
|
|
199
|
+
shellprint.panel(f"访问源 {url} 失败: {e}", "错误", "error")
|
|
86
200
|
return None
|
|
87
201
|
|
|
88
202
|
def add_source(self, value):
|
|
203
|
+
shellprint.status(f"验证源: {value}")
|
|
89
204
|
validated_url = asyncio.run(self._validate_url(value))
|
|
90
205
|
if not validated_url:
|
|
91
|
-
|
|
206
|
+
shellprint.panel("提供的源不是一个有效源,请检查后重试", "错误", "error")
|
|
92
207
|
return False
|
|
208
|
+
|
|
93
209
|
origins = env.get('origins')
|
|
94
210
|
if validated_url not in origins:
|
|
95
211
|
origins.append(validated_url)
|
|
96
212
|
env.set('origins', origins)
|
|
97
|
-
|
|
213
|
+
shellprint.panel(f"源 {validated_url} 已成功添加", "成功", "success")
|
|
98
214
|
return True
|
|
99
215
|
else:
|
|
100
|
-
|
|
216
|
+
shellprint.panel(f"源 {validated_url} 已存在,无需重复添加", "提示", "info")
|
|
101
217
|
return False
|
|
102
218
|
|
|
103
219
|
def update_sources(self):
|
|
220
|
+
shellprint.status("更新模块源...")
|
|
104
221
|
origins = env.get('origins')
|
|
105
222
|
providers = {}
|
|
106
223
|
modules = {}
|
|
107
224
|
module_alias = {}
|
|
108
225
|
table_rows = []
|
|
109
226
|
async def fetch_source_data():
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
227
|
+
for i, origin in enumerate(origins):
|
|
228
|
+
shellprint.status(f"获取源数据 ({i+1}/{len(origins)}): {origin}")
|
|
229
|
+
try:
|
|
230
|
+
async with aiohttp.ClientSession() as session:
|
|
114
231
|
async with session.get(origin) as response:
|
|
115
232
|
response.raise_for_status()
|
|
116
233
|
if response.headers.get('Content-Type', '').startswith('application/json'):
|
|
@@ -128,74 +245,176 @@ class SourceManager:
|
|
|
128
245
|
f"{providers[content['name']]}{module_origin_name}"
|
|
129
246
|
])
|
|
130
247
|
else:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
248
|
+
shellprint.panel(f"源 {origin} 返回的内容不是有效的 JSON 格式", "错误", "error")
|
|
249
|
+
except Exception as e:
|
|
250
|
+
shellprint.panel(f"获取 {origin} 时出错: {e}", "错误", "error")
|
|
251
|
+
|
|
134
252
|
asyncio.run(fetch_source_data())
|
|
135
|
-
|
|
253
|
+
shellprint.table(["源", "模块", "地址"], table_rows, "源更新状态", "success")
|
|
136
254
|
from datetime import datetime
|
|
137
255
|
env.set('providers', providers)
|
|
138
256
|
env.set('modules', modules)
|
|
139
257
|
env.set('module_alias', module_alias)
|
|
140
258
|
env.set('last_origin_update_time', datetime.now().isoformat())
|
|
141
|
-
|
|
259
|
+
shellprint.panel("源更新完成", "成功", "success")
|
|
142
260
|
|
|
143
261
|
def list_sources(self):
|
|
144
262
|
origins = env.get('origins')
|
|
145
263
|
if not origins:
|
|
146
|
-
|
|
264
|
+
shellprint.panel("当前没有配置任何源", "提示", "info")
|
|
147
265
|
return
|
|
266
|
+
|
|
148
267
|
rows = [[str(idx), origin] for idx, origin in enumerate(origins, 1)]
|
|
149
|
-
|
|
268
|
+
shellprint.table(["序号", "源地址"], rows, "已配置的源", "info")
|
|
150
269
|
|
|
151
270
|
def del_source(self, value):
|
|
152
271
|
origins = env.get('origins')
|
|
153
272
|
if value in origins:
|
|
154
273
|
origins.remove(value)
|
|
155
274
|
env.set('origins', origins)
|
|
156
|
-
|
|
275
|
+
shellprint.panel(f"源 {value} 已成功删除", "成功", "success")
|
|
157
276
|
else:
|
|
158
|
-
|
|
277
|
+
shellprint.panel(f"源 {value} 不存在", "错误", "error")
|
|
278
|
+
|
|
279
|
+
source_manager = SourceManager()
|
|
280
|
+
|
|
281
|
+
class ReloadHandler(FileSystemEventHandler):
|
|
282
|
+
def __init__(self, script_path, *args, **kwargs):
|
|
283
|
+
super().__init__(*args, **kwargs)
|
|
284
|
+
self.script_path = script_path
|
|
285
|
+
self.process = None
|
|
286
|
+
self.last_reload = time.time()
|
|
287
|
+
self.start_process()
|
|
288
|
+
|
|
289
|
+
def start_process(self):
|
|
290
|
+
if self.process:
|
|
291
|
+
self.process.terminate()
|
|
292
|
+
self.process.wait()
|
|
293
|
+
|
|
294
|
+
shellprint.status(f"启动进程: {self.script_path}")
|
|
295
|
+
self.process = subprocess.Popen([sys.executable, self.script_path])
|
|
296
|
+
self.last_reload = time.time()
|
|
297
|
+
|
|
298
|
+
def on_modified(self, event):
|
|
299
|
+
now = time.time()
|
|
300
|
+
# 1秒后再次触发
|
|
301
|
+
if now - self.last_reload < 1.0:
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
if event.src_path.endswith(".py"):
|
|
305
|
+
print(f"\n{Shell_Printer.CYAN}[热重载] 检测到文件变动: {event.src_path}{Shell_Printer.RESET}")
|
|
306
|
+
self.start_process()
|
|
307
|
+
|
|
308
|
+
def start_reloader(script_path):
|
|
309
|
+
project_root = os.path.dirname(os.path.abspath(__file__))
|
|
310
|
+
watch_dirs = [
|
|
311
|
+
os.path.dirname(os.path.abspath(script_path)),
|
|
312
|
+
os.path.join(project_root, "modules")
|
|
313
|
+
]
|
|
314
|
+
|
|
315
|
+
handler = ReloadHandler(script_path)
|
|
316
|
+
observer = Observer()
|
|
317
|
+
|
|
318
|
+
for d in watch_dirs:
|
|
319
|
+
if os.path.exists(d):
|
|
320
|
+
observer.schedule(handler, d, recursive=True)
|
|
321
|
+
|
|
322
|
+
observer.start()
|
|
323
|
+
print(f"\n{Shell_Printer.GREEN}{Shell_Printer.BOLD}[热重载] 已启动{Shell_Printer.RESET}")
|
|
324
|
+
print(f"{Shell_Printer.DIM}监控目录: {', '.join(watch_dirs)}{Shell_Printer.RESET}\n")
|
|
325
|
+
try:
|
|
326
|
+
while True:
|
|
327
|
+
time.sleep(1)
|
|
328
|
+
except KeyboardInterrupt:
|
|
329
|
+
observer.stop()
|
|
330
|
+
if handler.process:
|
|
331
|
+
handler.process.terminate()
|
|
332
|
+
observer.join()
|
|
159
333
|
|
|
160
334
|
def enable_module(module_name):
|
|
335
|
+
shellprint.status(f"启用模块: {module_name}")
|
|
161
336
|
module_info = mods.get_module(module_name)
|
|
162
337
|
if module_info:
|
|
163
338
|
mods.set_module_status(module_name, True)
|
|
164
|
-
|
|
339
|
+
shellprint.panel(f"模块 {module_name} 已成功启用", "成功", "success")
|
|
165
340
|
else:
|
|
166
|
-
|
|
341
|
+
shellprint.panel(f"模块 {module_name} 不存在", "错误", "error")
|
|
167
342
|
|
|
168
343
|
def disable_module(module_name):
|
|
344
|
+
shellprint.status(f"禁用模块: {module_name}")
|
|
169
345
|
module_info = mods.get_module(module_name)
|
|
170
346
|
if module_info:
|
|
171
347
|
mods.set_module_status(module_name, False)
|
|
172
|
-
|
|
348
|
+
shellprint.panel(f"模块 {module_name} 已成功禁用", "成功", "success")
|
|
173
349
|
else:
|
|
174
|
-
|
|
350
|
+
shellprint.panel(f"模块 {module_name} 不存在", "错误", "error")
|
|
175
351
|
|
|
176
|
-
async def fetch_url(session, url):
|
|
352
|
+
async def fetch_url(session, url, progress_callback=None):
|
|
177
353
|
try:
|
|
178
354
|
async with session.get(url) as response:
|
|
179
355
|
response.raise_for_status()
|
|
180
|
-
|
|
356
|
+
total_size = int(response.headers.get('Content-Length', 0))
|
|
357
|
+
downloaded = 0
|
|
358
|
+
data = b''
|
|
359
|
+
|
|
360
|
+
async for chunk in response.content.iter_any():
|
|
361
|
+
data += chunk
|
|
362
|
+
downloaded += len(chunk)
|
|
363
|
+
if total_size and progress_callback:
|
|
364
|
+
progress_callback(downloaded, total_size)
|
|
365
|
+
|
|
366
|
+
return data
|
|
181
367
|
except Exception as e:
|
|
182
368
|
print(f"请求失败: {e}")
|
|
183
369
|
return None
|
|
184
370
|
|
|
185
371
|
def extract_and_setup_module(module_name, module_url, zip_path, module_dir):
|
|
186
372
|
try:
|
|
187
|
-
print(f"
|
|
373
|
+
print(f"正在下载模块: {Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET}")
|
|
374
|
+
current_downloaded = [0]
|
|
375
|
+
total_size = [0]
|
|
376
|
+
|
|
377
|
+
def progress_callback(downloaded, total):
|
|
378
|
+
if total > 0 and total_size[0] == 0:
|
|
379
|
+
total_size[0] = total
|
|
380
|
+
if downloaded > current_downloaded[0]:
|
|
381
|
+
current_downloaded[0] = downloaded
|
|
382
|
+
prefix = f"{Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET} {Shell_Printer.DIM}下载中{Shell_Printer.RESET}"
|
|
383
|
+
suffix = f"{downloaded/1024:.1f}KB/{total/1024:.1f}KB" if total > 0 else f"{downloaded/1024:.1f}KB"
|
|
384
|
+
shellprint.progress_bar(downloaded, total or downloaded, prefix, suffix, 40)
|
|
385
|
+
|
|
188
386
|
async def download_module():
|
|
189
387
|
async with aiohttp.ClientSession() as session:
|
|
190
|
-
content = await fetch_url(session, module_url)
|
|
388
|
+
content = await fetch_url(session, module_url, progress_callback)
|
|
191
389
|
if content is None:
|
|
192
390
|
return False
|
|
391
|
+
|
|
193
392
|
with open(zip_path, 'wb') as zip_file:
|
|
194
393
|
zip_file.write(content)
|
|
394
|
+
|
|
395
|
+
if total_size[0] > 0:
|
|
396
|
+
shellprint.progress_bar(total_size[0], total_size[0],
|
|
397
|
+
f"{Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET}",
|
|
398
|
+
"下载完成", 40)
|
|
399
|
+
|
|
195
400
|
if not os.path.exists(module_dir):
|
|
196
401
|
os.makedirs(module_dir)
|
|
402
|
+
|
|
403
|
+
shellprint.status(f"解压模块: {module_name}")
|
|
197
404
|
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
|
198
|
-
zip_ref.
|
|
405
|
+
file_list = zip_ref.namelist()
|
|
406
|
+
for i, file in enumerate(file_list):
|
|
407
|
+
zip_ref.extract(file, module_dir)
|
|
408
|
+
if len(file_list) > 10 and i % (len(file_list) // 10) == 0:
|
|
409
|
+
shellprint.progress_bar(i, len(file_list),
|
|
410
|
+
f"{Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET}",
|
|
411
|
+
"解压中", 30)
|
|
412
|
+
if len(file_list) > 10:
|
|
413
|
+
shellprint.progress_bar(len(file_list), len(file_list),
|
|
414
|
+
f"{Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET}",
|
|
415
|
+
"解压完成", 30)
|
|
416
|
+
print()
|
|
417
|
+
|
|
199
418
|
init_file_path = os.path.join(module_dir, '__init__.py')
|
|
200
419
|
if not os.path.exists(init_file_path):
|
|
201
420
|
sub_module_dir = os.path.join(module_dir, module_name)
|
|
@@ -211,9 +430,10 @@ def extract_and_setup_module(module_name, module_url, zip_path, module_dir):
|
|
|
211
430
|
os.rmdir(sub_dir)
|
|
212
431
|
print(f"模块 {module_name} 文件已成功解压并设置")
|
|
213
432
|
return True
|
|
433
|
+
|
|
214
434
|
return asyncio.run(download_module())
|
|
215
435
|
except Exception as e:
|
|
216
|
-
|
|
436
|
+
shellprint.panel(f"处理模块 {module_name} 文件失败: {e}", "错误", "error")
|
|
217
437
|
if os.path.exists(zip_path):
|
|
218
438
|
try:
|
|
219
439
|
os.remove(zip_path)
|
|
@@ -230,45 +450,60 @@ def extract_and_setup_module(module_name, module_url, zip_path, module_dir):
|
|
|
230
450
|
def install_pip_dependencies(dependencies):
|
|
231
451
|
if not dependencies:
|
|
232
452
|
return True
|
|
233
|
-
|
|
453
|
+
|
|
454
|
+
print(f"{Shell_Printer.CYAN}正在安装pip依赖: {', '.join(dependencies)}{Shell_Printer.RESET}")
|
|
234
455
|
try:
|
|
235
|
-
|
|
456
|
+
# 使用子进程获取安装进度
|
|
457
|
+
process = subprocess.Popen(
|
|
236
458
|
[sys.executable, "-m", "pip", "install"] + dependencies,
|
|
237
|
-
check=True,
|
|
238
459
|
stdout=subprocess.PIPE,
|
|
239
|
-
stderr=subprocess.
|
|
460
|
+
stderr=subprocess.STDOUT,
|
|
461
|
+
universal_newlines=True
|
|
240
462
|
)
|
|
241
|
-
|
|
242
|
-
|
|
463
|
+
|
|
464
|
+
# 实时输出安装过程
|
|
465
|
+
while True:
|
|
466
|
+
output = process.stdout.readline()
|
|
467
|
+
if output == '' and process.poll() is not None:
|
|
468
|
+
break
|
|
469
|
+
if output:
|
|
470
|
+
print(f"{Shell_Printer.DIM}{output.strip()}{Shell_Printer.RESET}")
|
|
471
|
+
|
|
472
|
+
return process.returncode == 0
|
|
473
|
+
|
|
243
474
|
except subprocess.CalledProcessError as e:
|
|
244
|
-
|
|
475
|
+
shellprint.panel(f"安装pip依赖失败: {e.stderr}", "错误", "error")
|
|
245
476
|
return False
|
|
246
477
|
|
|
247
478
|
def install_module(module_name, force=False):
|
|
248
|
-
|
|
479
|
+
shellprint.panel(f"准备安装模块: {Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET}", "安装摘要", "info")
|
|
249
480
|
last_update_time = env.get('last_origin_update_time', None)
|
|
250
481
|
if last_update_time:
|
|
251
482
|
from datetime import datetime, timedelta
|
|
252
483
|
last_update = datetime.fromisoformat(last_update_time)
|
|
253
484
|
if datetime.now() - last_update > timedelta(hours=720):
|
|
254
|
-
|
|
255
|
-
if confirm("是否在安装模块前更新源?", default=True):
|
|
256
|
-
|
|
485
|
+
shellprint.panel("距离上次源更新已超过30天,源内可能有新模块或更新。", "提示", "warning")
|
|
486
|
+
if shellprint.confirm("是否在安装模块前更新源?", default=True):
|
|
487
|
+
source_manager.update_sources()
|
|
257
488
|
env.set('last_origin_update_time', datetime.now().isoformat())
|
|
258
|
-
|
|
489
|
+
shellprint.status("源更新完成")
|
|
490
|
+
|
|
259
491
|
module_info = mods.get_module(module_name)
|
|
260
492
|
if module_info and not force:
|
|
261
493
|
meta = module_info.get('info', {}).get('meta', {})
|
|
262
|
-
|
|
263
|
-
f"
|
|
264
|
-
"模块已存在"
|
|
494
|
+
shellprint.panel(
|
|
495
|
+
f"{Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET}\n版本: {meta.get('version', '未知')}\n描述: {meta.get('description', '无描述')}",
|
|
496
|
+
"模块已存在",
|
|
497
|
+
"info"
|
|
265
498
|
)
|
|
266
|
-
if not confirm("是否要强制重新安装?", default=False):
|
|
499
|
+
if not shellprint.confirm("是否要强制重新安装?", default=False):
|
|
267
500
|
return
|
|
501
|
+
|
|
268
502
|
providers = env.get('providers', {})
|
|
269
503
|
if isinstance(providers, str):
|
|
270
504
|
providers = json.loads(providers)
|
|
271
505
|
module_info_list = []
|
|
506
|
+
|
|
272
507
|
for provider, url in providers.items():
|
|
273
508
|
module_key = f"{module_name}@{provider}"
|
|
274
509
|
modules_data = env.get('modules', {})
|
|
@@ -289,43 +524,57 @@ def install_module(module_name, force=False):
|
|
|
289
524
|
'optional_dependencies': depsinfo.get("optional", []),
|
|
290
525
|
'pip_dependencies': depsinfo.get("pip", [])
|
|
291
526
|
})
|
|
527
|
+
|
|
292
528
|
if not module_info_list:
|
|
293
|
-
|
|
529
|
+
shellprint.panel(f"未找到模块 {module_name}", "错误", "error")
|
|
294
530
|
if providers:
|
|
295
|
-
print("当前可用源:")
|
|
531
|
+
print(f"{Shell_Printer.BOLD}当前可用源:{Shell_Printer.RESET}")
|
|
296
532
|
for provider in providers:
|
|
297
|
-
print(f" - {provider}")
|
|
533
|
+
print(f" {Shell_Printer.CYAN}- {provider}{Shell_Printer.RESET}")
|
|
298
534
|
return
|
|
535
|
+
|
|
299
536
|
if len(module_info_list) > 1:
|
|
300
|
-
print(f"找到 {len(module_info_list)} 个源的 {module_name} 模块:")
|
|
537
|
+
print(f"找到 {Shell_Printer.BOLD}{len(module_info_list)}{Shell_Printer.RESET} 个源的 {Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET} 模块:")
|
|
301
538
|
rows = []
|
|
302
539
|
for i, info in enumerate(module_info_list):
|
|
303
540
|
rows.append([
|
|
304
|
-
|
|
541
|
+
f"{Shell_Printer.CYAN}{i+1}{Shell_Printer.RESET}",
|
|
542
|
+
info['provider'],
|
|
543
|
+
info['version'],
|
|
544
|
+
info['description'],
|
|
545
|
+
info['author']
|
|
305
546
|
])
|
|
306
|
-
|
|
547
|
+
shellprint.table(["编号", "源", "版本", "描述", "作者"], rows, "可选模块源", "info")
|
|
548
|
+
|
|
307
549
|
while True:
|
|
308
|
-
choice = ask("请选择要安装的源 (输入编号)",
|
|
309
|
-
if choice
|
|
550
|
+
choice = shellprint.ask("请选择要安装的源 (输入编号)", [str(i) for i in range(1, len(module_info_list)+1)])
|
|
551
|
+
if choice and 1 <= int(choice) <= len(module_info_list):
|
|
310
552
|
selected_module = module_info_list[int(choice)-1]
|
|
311
553
|
break
|
|
312
554
|
else:
|
|
313
|
-
print("输入无效,请重新选择")
|
|
555
|
+
print(f"{Shell_Printer.YELLOW}输入无效,请重新选择{Shell_Printer.RESET}")
|
|
314
556
|
else:
|
|
315
557
|
selected_module = module_info_list[0]
|
|
558
|
+
|
|
559
|
+
# 安装依赖
|
|
316
560
|
for dep in selected_module['dependencies']:
|
|
317
|
-
print(f"
|
|
561
|
+
print(f"\n{Shell_Printer.BOLD}处理依赖: {dep}{Shell_Printer.RESET}")
|
|
318
562
|
install_module(dep)
|
|
563
|
+
|
|
564
|
+
# 安装pip依赖
|
|
319
565
|
third_party_deps = selected_module.get('pip_dependencies', [])
|
|
320
566
|
if third_party_deps:
|
|
321
|
-
print(f"模块 {module_name} 需要以下pip依赖: {', '.join(third_party_deps)}")
|
|
567
|
+
print(f"{Shell_Printer.YELLOW}模块 {module_name} 需要以下pip依赖: {', '.join(third_party_deps)}{Shell_Printer.RESET}")
|
|
322
568
|
if not install_pip_dependencies(third_party_deps):
|
|
323
|
-
print(f"无法安装模块 {module_name} 的pip依赖,安装终止")
|
|
569
|
+
print(f"{Shell_Printer.RED}无法安装模块 {module_name} 的pip依赖,安装终止{Shell_Printer.RESET}")
|
|
324
570
|
return
|
|
571
|
+
|
|
572
|
+
# 下载并安装模块
|
|
325
573
|
module_url = selected_module['url'] + selected_module['path']
|
|
326
574
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
327
575
|
module_dir = os.path.join(script_dir, 'modules', module_name)
|
|
328
576
|
zip_path = os.path.join(script_dir, f"{module_name}.zip")
|
|
577
|
+
|
|
329
578
|
if not extract_and_setup_module(
|
|
330
579
|
module_name=module_name,
|
|
331
580
|
module_url=module_url,
|
|
@@ -333,6 +582,8 @@ def install_module(module_name, force=False):
|
|
|
333
582
|
module_dir=module_dir
|
|
334
583
|
):
|
|
335
584
|
return
|
|
585
|
+
|
|
586
|
+
# 注册模块信息
|
|
336
587
|
mods.set_module(module_name, {
|
|
337
588
|
'status': True,
|
|
338
589
|
'info': {
|
|
@@ -349,39 +600,50 @@ def install_module(module_name, force=False):
|
|
|
349
600
|
}
|
|
350
601
|
}
|
|
351
602
|
})
|
|
352
|
-
|
|
603
|
+
|
|
604
|
+
shellprint.panel(f"模块 {Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET} 安装成功", "成功", "success")
|
|
353
605
|
|
|
354
606
|
def uninstall_module(module_name):
|
|
355
|
-
|
|
607
|
+
shellprint.panel(f"准备卸载模块: {Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET}", "卸载摘要", "warning")
|
|
356
608
|
module_info = mods.get_module(module_name)
|
|
357
609
|
if not module_info:
|
|
358
|
-
|
|
610
|
+
shellprint.panel(f"模块 {module_name} 不存在", "错误", "error")
|
|
359
611
|
return
|
|
612
|
+
|
|
360
613
|
meta = module_info.get('info', {}).get('meta', {})
|
|
361
614
|
depsinfo = module_info.get('info', {}).get('dependencies', {})
|
|
362
|
-
|
|
363
|
-
f"版本: {meta.get('version', '未知')}
|
|
364
|
-
"
|
|
615
|
+
shellprint.panel(
|
|
616
|
+
f"版本: {Shell_Printer.BOLD}{meta.get('version', '未知')}{Shell_Printer.RESET}\n"
|
|
617
|
+
f"描述: {meta.get('description', '无描述')}\n"
|
|
618
|
+
f"pip依赖: {Shell_Printer.YELLOW}{', '.join(depsinfo.get('pip', [])) or '无'}{Shell_Printer.RESET}",
|
|
619
|
+
"模块信息",
|
|
620
|
+
"info"
|
|
365
621
|
)
|
|
366
|
-
|
|
367
|
-
|
|
622
|
+
|
|
623
|
+
if not shellprint.confirm("确认要卸载此模块吗?", default=False):
|
|
624
|
+
print(f"{Shell_Printer.BLUE}卸载已取消{Shell_Printer.RESET}")
|
|
368
625
|
return
|
|
626
|
+
|
|
369
627
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
370
628
|
module_path = os.path.join(script_dir, 'modules', module_name)
|
|
371
629
|
module_file_path = module_path + '.py'
|
|
630
|
+
|
|
372
631
|
if os.path.exists(module_file_path):
|
|
373
632
|
try:
|
|
633
|
+
shellprint.status(f"删除模块文件: {module_name}.py")
|
|
374
634
|
os.remove(module_file_path)
|
|
375
635
|
except Exception as e:
|
|
376
|
-
|
|
636
|
+
shellprint.panel(f"删除模块文件 {module_name} 时出错: {e}", "错误", "error")
|
|
377
637
|
elif os.path.exists(module_path) and os.path.isdir(module_path):
|
|
378
638
|
try:
|
|
639
|
+
shellprint.status(f"删除模块目录: {module_name}")
|
|
379
640
|
shutil.rmtree(module_path)
|
|
380
641
|
except Exception as e:
|
|
381
|
-
|
|
642
|
+
shellprint.panel(f"删除模块目录 {module_name} 时出错: {e}", "错误", "error")
|
|
382
643
|
else:
|
|
383
|
-
|
|
644
|
+
shellprint.panel(f"模块 {module_name} 文件不存在", "错误", "error")
|
|
384
645
|
return
|
|
646
|
+
|
|
385
647
|
pip_dependencies = depsinfo.get('pip', [])
|
|
386
648
|
if pip_dependencies:
|
|
387
649
|
all_modules = mods.get_all_modules()
|
|
@@ -389,55 +651,66 @@ def uninstall_module(module_name):
|
|
|
389
651
|
essential_packages = {'aiohttp'}
|
|
390
652
|
for dep in pip_dependencies:
|
|
391
653
|
if dep in essential_packages:
|
|
392
|
-
print(f"跳过必要模块 {dep} 的卸载")
|
|
654
|
+
print(f"{Shell_Printer.CYAN}跳过必要模块 {dep} 的卸载{Shell_Printer.RESET}")
|
|
393
655
|
continue
|
|
656
|
+
|
|
394
657
|
is_dependency_used = False
|
|
395
658
|
for name, info in all_modules.items():
|
|
396
659
|
if name != module_name and dep in info.get('info', {}).get('dependencies', {}).get('pip', []):
|
|
397
660
|
is_dependency_used = True
|
|
398
661
|
break
|
|
662
|
+
|
|
399
663
|
if not is_dependency_used:
|
|
400
664
|
unused_pip_dependencies.append(dep)
|
|
665
|
+
|
|
401
666
|
if unused_pip_dependencies:
|
|
402
|
-
|
|
403
|
-
f"以下 pip 依赖不再被其他模块使用:\n{', '.join(unused_pip_dependencies)}",
|
|
404
|
-
"可卸载依赖"
|
|
667
|
+
shellprint.panel(
|
|
668
|
+
f"以下 pip 依赖不再被其他模块使用:\n{Shell_Printer.YELLOW}{', '.join(unused_pip_dependencies)}{Shell_Printer.RESET}",
|
|
669
|
+
"可卸载依赖",
|
|
670
|
+
"info"
|
|
405
671
|
)
|
|
406
|
-
if confirm("是否卸载这些 pip 依赖?", default=False):
|
|
672
|
+
if shellprint.confirm("是否卸载这些 pip 依赖?", default=False):
|
|
407
673
|
try:
|
|
674
|
+
shellprint.status(f"卸载pip依赖: {', '.join(unused_pip_dependencies)}")
|
|
408
675
|
subprocess.run(
|
|
409
676
|
[sys.executable, "-m", "pip", "uninstall", "-y"] + unused_pip_dependencies,
|
|
410
677
|
check=True,
|
|
411
678
|
stdout=subprocess.PIPE,
|
|
412
679
|
stderr=subprocess.PIPE
|
|
413
680
|
)
|
|
414
|
-
|
|
681
|
+
shellprint.panel(
|
|
415
682
|
f"成功卸载 pip 依赖: {', '.join(unused_pip_dependencies)}",
|
|
416
|
-
"成功"
|
|
683
|
+
"成功",
|
|
684
|
+
"success"
|
|
417
685
|
)
|
|
418
686
|
except subprocess.CalledProcessError as e:
|
|
419
|
-
|
|
687
|
+
shellprint.panel(
|
|
420
688
|
f"卸载 pip 依赖失败: {e.stderr.decode()}",
|
|
421
|
-
"错误"
|
|
689
|
+
"错误",
|
|
690
|
+
"error"
|
|
422
691
|
)
|
|
692
|
+
|
|
423
693
|
if mods.remove_module(module_name):
|
|
424
|
-
|
|
694
|
+
shellprint.panel(f"模块 {Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET} 已成功卸载", "成功", "success")
|
|
425
695
|
else:
|
|
426
|
-
|
|
696
|
+
shellprint.panel(f"模块 {module_name} 不存在", "错误", "error")
|
|
427
697
|
|
|
428
698
|
def upgrade_all_modules(force=False):
|
|
429
699
|
all_modules = mods.get_all_modules()
|
|
430
700
|
if not all_modules:
|
|
431
|
-
print("未找到任何模块,无法更新")
|
|
701
|
+
print(f"{Shell_Printer.YELLOW}未找到任何模块,无法更新{Shell_Printer.RESET}")
|
|
432
702
|
return
|
|
703
|
+
|
|
433
704
|
providers = env.get('providers', {})
|
|
434
705
|
if isinstance(providers, str):
|
|
435
706
|
providers = json.loads(providers)
|
|
436
707
|
modules_data = env.get('modules', {})
|
|
437
708
|
if isinstance(modules_data, str):
|
|
438
709
|
modules_data = json.loads(modules_data)
|
|
710
|
+
|
|
439
711
|
updates_available = []
|
|
440
712
|
for module_name, module_info in all_modules.items():
|
|
713
|
+
shellprint.status(f"检查更新: {module_name}")
|
|
441
714
|
local_version = module_info.get('info', {}).get('meta', {}).get('version', '0.0.0')
|
|
442
715
|
for provider, url in providers.items():
|
|
443
716
|
module_key = f"{module_name}@{provider}"
|
|
@@ -453,24 +726,39 @@ def upgrade_all_modules(force=False):
|
|
|
453
726
|
'url': url,
|
|
454
727
|
'path': remote_module.get('path', ''),
|
|
455
728
|
})
|
|
729
|
+
|
|
456
730
|
if not updates_available:
|
|
457
|
-
print("所有模块已是最新版本,无需更新")
|
|
731
|
+
print(f"{Shell_Printer.GREEN}所有模块已是最新版本,无需更新{Shell_Printer.RESET}")
|
|
458
732
|
return
|
|
459
|
-
|
|
733
|
+
|
|
734
|
+
print(f"\n{Shell_Printer.BOLD}以下模块有可用更新:{Shell_Printer.RESET}")
|
|
460
735
|
rows = []
|
|
461
736
|
for update in updates_available:
|
|
462
|
-
rows.append([
|
|
463
|
-
|
|
737
|
+
rows.append([
|
|
738
|
+
update['name'],
|
|
739
|
+
update['local_version'],
|
|
740
|
+
f"{Shell_Printer.GREEN}{update['remote_version']}{Shell_Printer.RESET}",
|
|
741
|
+
update['provider']
|
|
742
|
+
])
|
|
743
|
+
shellprint.table(["模块", "当前版本", "最新版本", "源"], rows, "可用更新", "info")
|
|
744
|
+
|
|
464
745
|
if not force:
|
|
465
|
-
|
|
466
|
-
|
|
746
|
+
warning_msg = (
|
|
747
|
+
f"{Shell_Printer.BOLD}{Shell_Printer.RED}警告:{Shell_Printer.RESET} "
|
|
748
|
+
"更新模块可能会导致兼容性问题,请在更新前查看插件作者的相关声明。\n"
|
|
749
|
+
"是否继续?"
|
|
750
|
+
)
|
|
751
|
+
if not shellprint.confirm(warning_msg, default=False):
|
|
752
|
+
print(f"{Shell_Printer.BLUE}更新已取消{Shell_Printer.RESET}")
|
|
467
753
|
return
|
|
468
|
-
|
|
469
|
-
|
|
754
|
+
|
|
755
|
+
for i, update in enumerate(updates_available, 1):
|
|
756
|
+
print(f"\n{Shell_Printer.BOLD}[{i}/{len(updates_available)}]{Shell_Printer.RESET} 更新模块 {Shell_Printer.BOLD}{update['name']}{Shell_Printer.RESET}")
|
|
470
757
|
module_url = update['url'] + update['path']
|
|
471
758
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
472
759
|
module_dir = os.path.join(script_dir, 'modules', update['name'])
|
|
473
760
|
zip_path = os.path.join(script_dir, f"{update['name']}.zip")
|
|
761
|
+
|
|
474
762
|
if not extract_and_setup_module(
|
|
475
763
|
module_name=update['name'],
|
|
476
764
|
module_url=module_url,
|
|
@@ -478,27 +766,37 @@ def upgrade_all_modules(force=False):
|
|
|
478
766
|
module_dir=module_dir
|
|
479
767
|
):
|
|
480
768
|
continue
|
|
769
|
+
|
|
481
770
|
all_modules[update['name']]['info']['version'] = update['remote_version']
|
|
482
771
|
mods.set_all_modules(all_modules)
|
|
483
|
-
print(f"模块 {update['name']} 已更新至版本 {update['remote_version']}")
|
|
772
|
+
print(f"{Shell_Printer.GREEN}模块 {update['name']} 已更新至版本 {update['remote_version']}{Shell_Printer.RESET}")
|
|
773
|
+
|
|
774
|
+
shellprint.panel("所有可用更新已处理完成", "完成", "success")
|
|
484
775
|
|
|
485
776
|
def list_modules(module_name=None):
|
|
486
777
|
all_modules = mods.get_all_modules()
|
|
487
778
|
if not all_modules:
|
|
488
|
-
|
|
779
|
+
shellprint.panel("未在数据库中发现注册模块,正在初始化模块列表...", "提示", "info")
|
|
489
780
|
from . import init as init_module
|
|
490
781
|
init_module()
|
|
491
782
|
all_modules = mods.get_all_modules()
|
|
783
|
+
|
|
492
784
|
if not all_modules:
|
|
493
|
-
|
|
785
|
+
shellprint.panel("未找到任何模块", "错误", "error")
|
|
494
786
|
return
|
|
495
|
-
|
|
787
|
+
|
|
788
|
+
shellprint.panel(f"找到 {Shell_Printer.BOLD}{len(all_modules)}{Shell_Printer.RESET} 个模块", "统计", "info")
|
|
789
|
+
|
|
496
790
|
rows = []
|
|
497
791
|
for name, info in all_modules.items():
|
|
498
|
-
|
|
792
|
+
# 根据状态设置颜色
|
|
793
|
+
status_color = Shell_Printer.GREEN if info.get("status", True) else Shell_Printer.RED
|
|
794
|
+
status = f"{status_color}✓" if info.get("status", True) else f"{Shell_Printer.RED}✗"
|
|
795
|
+
|
|
499
796
|
meta = info.get('info', {}).get('meta', {})
|
|
500
797
|
depsinfo = info.get('info', {}).get('dependencies', {})
|
|
501
798
|
optional_deps = depsinfo.get('optional', [])
|
|
799
|
+
|
|
502
800
|
available_optional_deps = []
|
|
503
801
|
missing_optional_deps = []
|
|
504
802
|
if optional_deps:
|
|
@@ -513,183 +811,287 @@ def list_modules(module_name=None):
|
|
|
513
811
|
available_optional_deps.append(dep)
|
|
514
812
|
else:
|
|
515
813
|
missing_optional_deps.append(dep)
|
|
814
|
+
|
|
516
815
|
if missing_optional_deps:
|
|
517
|
-
optional_dependencies = f"可用: {', '.join(available_optional_deps)} 缺失: {', '.join(missing_optional_deps)}"
|
|
816
|
+
optional_dependencies = f"可用: {', '.join(available_optional_deps)} 缺失: {Shell_Printer.RED}{', '.join(missing_optional_deps)}{Shell_Printer.RESET}"
|
|
518
817
|
else:
|
|
519
818
|
optional_dependencies = ', '.join(available_optional_deps) or '无'
|
|
520
819
|
else:
|
|
521
820
|
optional_dependencies = '无'
|
|
522
|
-
|
|
523
|
-
|
|
821
|
+
|
|
822
|
+
# 依赖项使用不同颜色标识
|
|
823
|
+
dependencies = Shell_Printer.YELLOW + ', '.join(depsinfo.get('requires', [])) + Shell_Printer.RESET or '无'
|
|
824
|
+
pip_dependencies = Shell_Printer.CYAN + ', '.join(depsinfo.get('pip', [])) + Shell_Printer.RESET or '无'
|
|
825
|
+
|
|
524
826
|
rows.append([
|
|
525
|
-
|
|
526
|
-
|
|
827
|
+
Shell_Printer.BOLD + name + Shell_Printer.RESET,
|
|
828
|
+
status,
|
|
829
|
+
Shell_Printer.BLUE + meta.get('version', '未知') + Shell_Printer.RESET,
|
|
830
|
+
meta.get('description', '无描述'),
|
|
831
|
+
dependencies,
|
|
832
|
+
optional_dependencies,
|
|
833
|
+
pip_dependencies
|
|
527
834
|
])
|
|
528
|
-
|
|
835
|
+
|
|
836
|
+
shellprint.table(
|
|
529
837
|
["模块名称", "状态", "版本", "描述", "依赖", "可选依赖", "pip依赖"],
|
|
530
838
|
rows,
|
|
531
|
-
"模块列表"
|
|
839
|
+
"模块列表",
|
|
840
|
+
"info"
|
|
532
841
|
)
|
|
842
|
+
|
|
533
843
|
enabled_count = sum(1 for m in all_modules.values() if m.get("status", True))
|
|
534
844
|
disabled_count = len(all_modules) - enabled_count
|
|
535
|
-
|
|
845
|
+
shellprint.panel(
|
|
846
|
+
f"{Shell_Printer.GREEN}已启用: {enabled_count}{Shell_Printer.RESET} "
|
|
847
|
+
f"{Shell_Printer.RED}已禁用: {disabled_count}{Shell_Printer.RESET}",
|
|
848
|
+
"模块状态统计",
|
|
849
|
+
"info"
|
|
850
|
+
)
|
|
536
851
|
|
|
537
852
|
def main():
|
|
538
853
|
parser = argparse.ArgumentParser(
|
|
539
|
-
|
|
540
|
-
prog="
|
|
854
|
+
|
|
855
|
+
prog="epsdk",
|
|
856
|
+
formatter_class=argparse.RawTextHelpFormatter
|
|
541
857
|
)
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
858
|
+
parser._positionals.title = f"{Shell_Printer.BOLD}{Shell_Printer.CYAN}基本命令{Shell_Printer.RESET}"
|
|
859
|
+
parser._optionals.title = f"{Shell_Printer.BOLD}{Shell_Printer.MAGENTA}可选参数{Shell_Printer.RESET}"
|
|
860
|
+
|
|
861
|
+
subparsers = parser.add_subparsers(
|
|
862
|
+
dest='command',
|
|
863
|
+
title='可用的命令',
|
|
864
|
+
metavar=f"{Shell_Printer.GREEN}<命令>{Shell_Printer.RESET}",
|
|
865
|
+
help='具体命令的帮助信息'
|
|
866
|
+
)
|
|
867
|
+
|
|
868
|
+
command_help = {
|
|
869
|
+
'enable': '启用指定模块',
|
|
870
|
+
'disable': '禁用指定模块',
|
|
871
|
+
'list': '列出所有模块信息',
|
|
872
|
+
'update': '更新模块列表',
|
|
873
|
+
'upgrade': '升级所有可用模块',
|
|
874
|
+
'uninstall': '删除指定模块',
|
|
875
|
+
'install': '安装指定模块',
|
|
876
|
+
'origin': '管理模块源',
|
|
877
|
+
'run': '运行指定主程序'
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
# 模块管理
|
|
881
|
+
def add_module_command(name, help_text):
|
|
882
|
+
cmd = subparsers.add_parser(name, help=help_text, description=help_text)
|
|
883
|
+
cmd.add_argument('module_names', nargs='+', help='模块名称')
|
|
884
|
+
if name in ['enable', 'disable', 'install']:
|
|
885
|
+
cmd.add_argument('--init', action='store_true', help='在操作前初始化模块数据库')
|
|
886
|
+
if name in ['install']:
|
|
887
|
+
cmd.add_argument('--force', action='store_true', help='强制重新安装模块')
|
|
888
|
+
return cmd
|
|
889
|
+
|
|
890
|
+
enable_parser = add_module_command('enable', '启用指定模块')
|
|
891
|
+
disable_parser = add_module_command('disable', '禁用指定模块')
|
|
892
|
+
uninstall_parser = add_module_command('uninstall', '删除指定模块')
|
|
893
|
+
install_parser = add_module_command('install', '安装指定模块')
|
|
894
|
+
|
|
895
|
+
# 其他命令
|
|
549
896
|
list_parser = subparsers.add_parser('list', help='列出所有模块信息')
|
|
550
897
|
list_parser.add_argument('--module', '-m', type=str, help='指定要展示的模块名称')
|
|
898
|
+
|
|
551
899
|
update_parser = subparsers.add_parser('update', help='更新模块列表')
|
|
900
|
+
|
|
552
901
|
upgrade_parser = subparsers.add_parser('upgrade', help='升级模块列表')
|
|
553
902
|
upgrade_parser.add_argument('--force', action='store_true', help='跳过二次确认,强制更新')
|
|
554
|
-
|
|
555
|
-
uninstall_parser.add_argument('module_names', nargs='+', help='要卸载的模块名称(支持多个模块,用空格分隔)')
|
|
556
|
-
install_parser = subparsers.add_parser('install', help='安装指定模块(支持多个模块,用空格分隔)')
|
|
557
|
-
install_parser.add_argument('module_name', nargs='+', help='要安装的模块名称(支持多个模块,用空格分隔)')
|
|
558
|
-
install_parser.add_argument('--force', action='store_true', help='强制重新安装模块')
|
|
559
|
-
install_parser.add_argument('--init', action='store_true', help='在安装模块前初始化模块数据库')
|
|
903
|
+
|
|
560
904
|
origin_parser = subparsers.add_parser('origin', help='管理模块源')
|
|
561
|
-
origin_subparsers = origin_parser.add_subparsers(
|
|
905
|
+
origin_subparsers = origin_parser.add_subparsers(
|
|
906
|
+
dest='origin_command',
|
|
907
|
+
title='源管理命令',
|
|
908
|
+
metavar=f"{Shell_Printer.CYAN}<子命令>{Shell_Printer.RESET}"
|
|
909
|
+
)
|
|
910
|
+
|
|
562
911
|
add_origin_parser = origin_subparsers.add_parser('add', help='添加模块源')
|
|
563
912
|
add_origin_parser.add_argument('url', type=str, help='要添加的模块源URL')
|
|
913
|
+
|
|
564
914
|
list_origin_parser = origin_subparsers.add_parser('list', help='列出所有模块源')
|
|
915
|
+
|
|
565
916
|
del_origin_parser = origin_subparsers.add_parser('del', help='删除模块源')
|
|
566
917
|
del_origin_parser.add_argument('url', type=str, help='要删除的模块源URL')
|
|
918
|
+
|
|
919
|
+
run_parser = subparsers.add_parser('run', help='运行指定主程序')
|
|
920
|
+
run_parser.add_argument('script', type=str, help='要运行的主程序路径')
|
|
921
|
+
run_parser.add_argument('--reload', action='store_true', help='启用热重载模式,自动检测代码变动并重启')
|
|
922
|
+
|
|
567
923
|
args = parser.parse_args()
|
|
568
|
-
|
|
924
|
+
|
|
569
925
|
if hasattr(args, 'init') and args.init:
|
|
570
|
-
print("正在初始化模块列表...")
|
|
926
|
+
print(f"{Shell_Printer.GREEN}正在初始化模块列表...{Shell_Printer.RESET}")
|
|
571
927
|
from . import init as init_module
|
|
572
928
|
init_module()
|
|
929
|
+
print(f"{Shell_Printer.GREEN}模块列表初始化完成{Shell_Printer.RESET}")
|
|
930
|
+
|
|
931
|
+
# 处理命令
|
|
573
932
|
if args.command == 'enable':
|
|
574
933
|
for module_name in args.module_names:
|
|
575
934
|
module_name = module_name.strip()
|
|
576
935
|
if not module_name:
|
|
577
936
|
continue
|
|
937
|
+
|
|
578
938
|
if '*' in module_name or '?' in module_name:
|
|
579
|
-
|
|
939
|
+
shellprint.status(f"匹配模块模式: {module_name}")
|
|
580
940
|
all_modules = mods.get_all_modules()
|
|
581
941
|
if not all_modules:
|
|
582
|
-
|
|
942
|
+
shellprint.panel("未找到任何模块,请先更新源或检查配置", "错误", "error")
|
|
583
943
|
continue
|
|
944
|
+
|
|
584
945
|
matched_modules = [name for name in all_modules.keys() if fnmatch.fnmatch(name, module_name)]
|
|
585
946
|
if not matched_modules:
|
|
586
|
-
|
|
947
|
+
shellprint.panel(f"未找到匹配模块模式 {module_name} 的模块", "错误", "error")
|
|
587
948
|
continue
|
|
588
|
-
|
|
949
|
+
|
|
950
|
+
print(f"{Shell_Printer.GREEN}找到 {len(matched_modules)} 个匹配模块:{Shell_Printer.RESET}")
|
|
589
951
|
for i, matched_module in enumerate(matched_modules, start=1):
|
|
590
|
-
print(f" {i}. {matched_module}")
|
|
591
|
-
|
|
592
|
-
|
|
952
|
+
print(f" {Shell_Printer.CYAN}{i}. {matched_module}{Shell_Printer.RESET}")
|
|
953
|
+
|
|
954
|
+
if not shellprint.confirm("是否启用所有匹配模块?", default=True):
|
|
955
|
+
print(f"{Shell_Printer.BLUE}操作已取消{Shell_Printer.RESET}")
|
|
593
956
|
continue
|
|
957
|
+
|
|
594
958
|
for matched_module in matched_modules:
|
|
595
959
|
enable_module(matched_module)
|
|
596
960
|
else:
|
|
597
961
|
enable_module(module_name)
|
|
962
|
+
|
|
598
963
|
elif args.command == 'disable':
|
|
599
964
|
for module_name in args.module_names:
|
|
600
965
|
module_name = module_name.strip()
|
|
601
966
|
if not module_name:
|
|
602
967
|
continue
|
|
968
|
+
|
|
603
969
|
if '*' in module_name or '?' in module_name:
|
|
604
|
-
|
|
970
|
+
shellprint.status(f"匹配模块模式: {module_name}")
|
|
605
971
|
all_modules = mods.get_all_modules()
|
|
606
972
|
if not all_modules:
|
|
607
|
-
|
|
973
|
+
shellprint.panel("未找到任何模块,请先更新源或检查配置", "错误", "error")
|
|
608
974
|
continue
|
|
975
|
+
|
|
609
976
|
matched_modules = [name for name in all_modules.keys() if fnmatch.fnmatch(name, module_name)]
|
|
610
977
|
if not matched_modules:
|
|
611
|
-
|
|
978
|
+
shellprint.panel(f"未找到匹配模块模式 {module_name} 的模块", "错误", "error")
|
|
612
979
|
continue
|
|
613
|
-
|
|
980
|
+
|
|
981
|
+
print(f"{Shell_Printer.GREEN}找到 {len(matched_modules)} 个匹配模块:{Shell_Printer.RESET}")
|
|
614
982
|
for i, matched_module in enumerate(matched_modules, start=1):
|
|
615
|
-
print(f" {i}. {matched_module}")
|
|
616
|
-
|
|
617
|
-
|
|
983
|
+
print(f" {Shell_Printer.CYAN}{i}. {matched_module}{Shell_Printer.RESET}")
|
|
984
|
+
|
|
985
|
+
if not shellprint.confirm("是否禁用所有匹配模块?", default=True):
|
|
986
|
+
print(f"{Shell_Printer.BLUE}操作已取消{Shell_Printer.RESET}")
|
|
618
987
|
continue
|
|
988
|
+
|
|
619
989
|
for matched_module in matched_modules:
|
|
620
990
|
disable_module(matched_module)
|
|
621
991
|
else:
|
|
622
992
|
disable_module(module_name)
|
|
993
|
+
|
|
623
994
|
elif args.command == 'list':
|
|
624
995
|
list_modules(args.module)
|
|
996
|
+
|
|
625
997
|
elif args.command == 'uninstall':
|
|
626
998
|
for module_name in args.module_names:
|
|
627
999
|
module_name = module_name.strip()
|
|
628
1000
|
if not module_name:
|
|
629
1001
|
continue
|
|
1002
|
+
|
|
630
1003
|
if '*' in module_name or '?' in module_name:
|
|
631
|
-
|
|
1004
|
+
shellprint.status(f"匹配模块模式: {module_name}")
|
|
632
1005
|
all_modules = mods.get_all_modules()
|
|
633
1006
|
if not all_modules:
|
|
634
|
-
|
|
1007
|
+
shellprint.panel("未找到任何模块,请先更新源或检查配置", "错误", "error")
|
|
635
1008
|
continue
|
|
1009
|
+
|
|
636
1010
|
matched_modules = [name for name in all_modules.keys() if fnmatch.fnmatch(name, module_name)]
|
|
637
1011
|
if not matched_modules:
|
|
638
|
-
|
|
1012
|
+
shellprint.panel(f"未找到匹配模块模式 {module_name} 的模块", "错误", "error")
|
|
639
1013
|
continue
|
|
640
|
-
|
|
1014
|
+
|
|
1015
|
+
print(f"{Shell_Printer.GREEN}找到 {len(matched_modules)} 个匹配模块:{Shell_Printer.RESET}")
|
|
641
1016
|
for i, matched_module in enumerate(matched_modules, start=1):
|
|
642
|
-
print(f" {i}. {matched_module}")
|
|
643
|
-
|
|
644
|
-
|
|
1017
|
+
print(f" {Shell_Printer.CYAN}{i}. {matched_module}{Shell_Printer.RESET}")
|
|
1018
|
+
|
|
1019
|
+
if not shellprint.confirm("是否卸载所有匹配模块?", default=True):
|
|
1020
|
+
print(f"{Shell_Printer.BLUE}操作已取消{Shell_Printer.RESET}")
|
|
645
1021
|
continue
|
|
1022
|
+
|
|
646
1023
|
for matched_module in matched_modules:
|
|
647
1024
|
uninstall_module(matched_module)
|
|
648
1025
|
else:
|
|
649
1026
|
uninstall_module(module_name)
|
|
1027
|
+
|
|
650
1028
|
elif args.command == 'install':
|
|
651
|
-
for module_name in args.
|
|
1029
|
+
for module_name in args.module_names:
|
|
652
1030
|
module_name = module_name.strip()
|
|
653
1031
|
if not module_name:
|
|
654
1032
|
continue
|
|
1033
|
+
|
|
655
1034
|
if '*' in module_name or '?' in module_name:
|
|
656
|
-
|
|
1035
|
+
shellprint.status(f"匹配模块模式: {module_name}")
|
|
657
1036
|
all_modules = mods.get_all_modules()
|
|
658
1037
|
if not all_modules:
|
|
659
|
-
|
|
1038
|
+
shellprint.panel("未找到任何模块,请先更新源或检查配置", "错误", "error")
|
|
660
1039
|
continue
|
|
1040
|
+
|
|
661
1041
|
matched_modules = [name for name in all_modules.keys() if fnmatch.fnmatch(name, module_name)]
|
|
662
1042
|
if not matched_modules:
|
|
663
|
-
|
|
1043
|
+
shellprint.panel(f"未找到匹配模块模式 {module_name} 的模块", "错误", "error")
|
|
664
1044
|
continue
|
|
665
|
-
|
|
1045
|
+
|
|
1046
|
+
print(f"{Shell_Printer.GREEN}找到 {len(matched_modules)} 个匹配模块:{Shell_Printer.RESET}")
|
|
666
1047
|
for i, matched_module in enumerate(matched_modules, start=1):
|
|
667
|
-
print(f" {i}. {matched_module}")
|
|
668
|
-
|
|
669
|
-
|
|
1048
|
+
print(f" {Shell_Printer.CYAN}{i}. {matched_module}{Shell_Printer.RESET}")
|
|
1049
|
+
|
|
1050
|
+
if not shellprint.confirm("是否安装所有匹配模块?", default=True):
|
|
1051
|
+
print(f"{Shell_Printer.BLUE}安装已取消{Shell_Printer.RESET}")
|
|
670
1052
|
continue
|
|
1053
|
+
|
|
671
1054
|
for matched_module in matched_modules:
|
|
672
1055
|
install_module(matched_module, args.force)
|
|
673
1056
|
else:
|
|
674
1057
|
install_module(module_name, args.force)
|
|
1058
|
+
|
|
675
1059
|
elif args.command == 'update':
|
|
676
|
-
|
|
1060
|
+
source_manager.update_sources()
|
|
1061
|
+
|
|
677
1062
|
elif args.command == 'upgrade':
|
|
678
1063
|
upgrade_all_modules(args.force)
|
|
1064
|
+
|
|
1065
|
+
elif args.command == 'run':
|
|
1066
|
+
script_path = args.script
|
|
1067
|
+
if not os.path.exists(script_path):
|
|
1068
|
+
shellprint.panel(f"找不到指定文件: {script_path}", "错误", "error")
|
|
1069
|
+
return
|
|
1070
|
+
|
|
1071
|
+
if args.reload:
|
|
1072
|
+
start_reloader(script_path)
|
|
1073
|
+
else:
|
|
1074
|
+
shellprint.panel(f"运行脚本: {Shell_Printer.BOLD}{script_path}{Shell_Printer.RESET}", "执行", "info")
|
|
1075
|
+
import runpy
|
|
1076
|
+
runpy.run_path(script_path, run_name="__main__")
|
|
1077
|
+
|
|
679
1078
|
elif args.command == 'origin':
|
|
680
1079
|
if args.origin_command == 'add':
|
|
681
1080
|
success = source_manager.add_source(args.url)
|
|
682
|
-
if success:
|
|
683
|
-
|
|
684
|
-
|
|
1081
|
+
if success and shellprint.confirm("源已添加,是否立即更新源以获取最新模块信息?", default=True):
|
|
1082
|
+
source_manager.update_sources()
|
|
1083
|
+
|
|
685
1084
|
elif args.origin_command == 'list':
|
|
686
1085
|
source_manager.list_sources()
|
|
1086
|
+
|
|
687
1087
|
elif args.origin_command == 'del':
|
|
688
1088
|
source_manager.del_source(args.url)
|
|
1089
|
+
|
|
689
1090
|
else:
|
|
690
1091
|
origin_parser.print_help()
|
|
691
1092
|
else:
|
|
1093
|
+
# 如果没有提供命令,显示帮助信息
|
|
692
1094
|
parser.print_help()
|
|
693
1095
|
|
|
694
1096
|
if __name__ == "__main__":
|
|
695
|
-
main()
|
|
1097
|
+
main()
|