ErisPulse 1.0.13__py3-none-any.whl → 1.0.14.dev2__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/__main__.py CHANGED
@@ -1,931 +1,694 @@
1
- import argparse
2
- import os
3
- import sys
4
- import shutil
5
- import aiohttp
6
- import zipfile
7
- import fnmatch
8
- import asyncio
9
- import subprocess
10
- import json
11
- from rich.console import Console
12
- from rich.table import Table
13
- from rich.panel import Panel
14
- from rich.prompt import Prompt, Confirm
15
- from rich import box
16
- from prompt_toolkit import PromptSession
17
- from prompt_toolkit.completion import WordCompleter
18
- from prompt_toolkit.formatted_text import HTML
19
- from .envManager import env
20
-
21
- console = Console()
22
- class SourceManager:
23
- def __init__(self):
24
- self._init_sources()
25
-
26
- def _init_sources(self):
27
- if not env.get('origins'):
28
- env.set('origins', [])
29
-
30
- async def _validate_url(self, url):
31
- if not url.startswith(('http://', 'https://')):
32
- protocol = Prompt.ask("未指定协议,请输入使用的协议",
33
- choices=['http', 'https'],
34
- default="https")
35
- url = f"{protocol}://{url}"
36
-
37
- if not url.endswith('.json'):
38
- url = f"{url}/map.json"
39
-
40
- try:
41
- async with aiohttp.ClientSession() as session:
42
- async with session.get(url) as response:
43
- response.raise_for_status()
44
- if response.headers.get('Content-Type', '').startswith('application/json'):
45
- return url
46
- else:
47
- console.print(Panel(
48
- f"[red]源 {url} 返回的内容不是有效的 JSON 格式[/red]",
49
- title="错误",
50
- border_style="red"
51
- ))
52
- return None
53
- except Exception as e:
54
- console.print(Panel(
55
- f"[red]访问源 {url} 失败: {e}[/red]",
56
- title="错误",
57
- border_style="red"
58
- ))
59
- return None
60
-
61
- def add_source(self, value):
62
- validated_url = asyncio.run(self._validate_url(value))
63
- if not validated_url:
64
- console.print(Panel(
65
- "[red]提供的源不是一个有效源,请检查后重试[/red]",
66
- title="错误",
67
- border_style="red"
68
- ))
69
- return False
70
-
71
- origins = env.get('origins')
72
- if validated_url not in origins:
73
- origins.append(validated_url)
74
- env.set('origins', origins)
75
- console.print(Panel(
76
- f"[green]源 {validated_url} 已成功添加[/green]",
77
- border_style="green"
78
- ))
79
- return True
80
- else:
81
- console.print(Panel(
82
- f"[yellow]源 {validated_url} 已存在,无需重复添加[/yellow]",
83
- border_style="yellow"
84
- ))
85
- return False
86
-
87
- def update_sources(self):
88
- origins = env.get('origins')
89
- providers = {}
90
- modules = {}
91
- module_alias = {}
92
-
93
- table = Table(
94
- title="源更新状态",
95
- show_header=True,
96
- header_style="bold magenta",
97
- box=box.ROUNDED
98
- )
99
- table.add_column("源", style="cyan")
100
- table.add_column("模块", style="green")
101
- table.add_column("地址", style="blue")
102
-
103
- async def fetch_source_data():
104
- async with aiohttp.ClientSession() as session:
105
- for origin in origins:
106
- console.print(f"[cyan]正在获取 {origin}...[/cyan]")
107
- try:
108
- async with session.get(origin) as response:
109
- response.raise_for_status()
110
- if response.headers.get('Content-Type', '').startswith('application/json'):
111
- content = await response.json()
112
- providers[content["name"]] = content["base"]
113
-
114
- for module in content["modules"].keys():
115
- module_content = content["modules"][module]
116
- modules[f'{module}@{content["name"]}'] = module_content
117
- module_origin_name = module_content["path"]
118
- module_alias_name = module
119
- module_alias[f'{module_origin_name}@{content["name"]}'] = module_alias_name
120
-
121
- table.add_row(
122
- content['name'],
123
- module,
124
- f"{providers[content['name']]}{module_origin_name}"
125
- )
126
- else:
127
- console.print(Panel(
128
- f"[red]源 {origin} 返回的内容不是有效的 JSON 格式[/red]",
129
- title="错误",
130
- border_style="red"
131
- ))
132
- except Exception as e:
133
- console.print(Panel(
134
- f"[red]获取 {origin} 时出错: {e}[/red]",
135
- title="错误",
136
- border_style="red"
137
- ))
138
-
139
- asyncio.run(fetch_source_data())
140
- console.print(table)
141
- from datetime import datetime
142
- env.set('providers', providers)
143
- env.set('modules', modules)
144
- env.set('module_alias', module_alias)
145
- env.set('last_origin_update_time', datetime.now().isoformat())
146
-
147
- console.print(Panel(
148
- "[green]源更新完成[/green]",
149
- border_style="green"
150
- ))
151
-
152
- def list_sources(self):
153
- origins = env.get('origins')
154
- if not origins:
155
- console.print(Panel(
156
- "[yellow]当前没有配置任何源[/yellow]",
157
- border_style="yellow"
158
- ))
159
- return
160
-
161
- table = Table(title="已配置的源", show_header=True, header_style="bold magenta")
162
- table.add_column("序号", style="cyan", justify="center")
163
- table.add_column("源地址", style="green")
164
-
165
- for idx, origin in enumerate(origins, 1):
166
- table.add_row(str(idx), origin)
167
-
168
- console.print(table)
169
-
170
- def del_source(self, value):
171
- origins = env.get('origins')
172
- if value in origins:
173
- origins.remove(value)
174
- env.set('origins', origins)
175
- console.print(Panel(
176
- f"[green]源 {value} 已成功删除[/green]",
177
- border_style="green"
178
- ))
179
- else:
180
- console.print(Panel(
181
- f"[red]源 {value} 不存在[/red]",
182
- title="错误",
183
- border_style="red"
184
- ))
185
- def enable_module(module_name):
186
- module_info = env.get_module(module_name)
187
- if module_info:
188
- with console.status(f"[cyan]正在启用模块 {module_name}...[/cyan]"):
189
- env.set_module_status(module_name, True)
190
- console.print(Panel(
191
- f"[green]✓ 模块 {module_name} 已成功启用[/green]",
192
- border_style="green"
193
- ))
194
- else:
195
- console.print(Panel(
196
- f"[red]模块 {module_name} 不存在[/red]",
197
- title="错误",
198
- border_style="red"
199
- ))
200
-
201
- def disable_module(module_name):
202
- module_info = env.get_module(module_name)
203
- if module_info:
204
- with console.status(f"[cyan]正在禁用模块 {module_name}...[/cyan]"):
205
- env.set_module_status(module_name, False)
206
- console.print(Panel(
207
- f"[yellow]✓ 模块 {module_name} 已成功禁用[/yellow]",
208
- border_style="yellow"
209
- ))
210
- else:
211
- console.print(Panel(
212
- f"[red]模块 {module_name} 不存在[/red]",
213
- title="错误",
214
- border_style="red"
215
- ))
216
-
217
- async def fetch_url(session, url):
218
- try:
219
- async with session.get(url) as response:
220
- response.raise_for_status()
221
- return await response.read()
222
- except Exception as e:
223
- console.print(f"[red]请求失败: {e}[/red]")
224
- return None
225
-
226
- def extract_and_setup_module(module_name, module_url, zip_path, module_dir):
227
- try:
228
- console.print(f"[cyan]正在从 {module_url} 下载模块...[/cyan]")
229
-
230
- async def download_module():
231
- async with aiohttp.ClientSession() as session:
232
- content = await fetch_url(session, module_url)
233
- if content is None:
234
- return False
235
-
236
- with open(zip_path, 'wb') as zip_file:
237
- zip_file.write(content)
238
-
239
- if not os.path.exists(module_dir):
240
- os.makedirs(module_dir)
241
-
242
- with zipfile.ZipFile(zip_path, 'r') as zip_ref:
243
- zip_ref.extractall(module_dir)
244
-
245
- init_file_path = os.path.join(module_dir, '__init__.py')
246
- if not os.path.exists(init_file_path):
247
- sub_module_dir = os.path.join(module_dir, module_name)
248
- m_sub_module_dir = os.path.join(module_dir, f"m_{module_name}")
249
- for sub_dir in [sub_module_dir, m_sub_module_dir]:
250
- if os.path.exists(sub_dir) and os.path.isdir(sub_dir):
251
- for item in os.listdir(sub_dir):
252
- source_item = os.path.join(sub_dir, item)
253
- target_item = os.path.join(module_dir, item)
254
- if os.path.exists(target_item):
255
- os.remove(target_item)
256
- shutil.move(source_item, module_dir)
257
- os.rmdir(sub_dir)
258
-
259
- console.print(f"[green]模块 {module_name} 文件已成功解压并设置[/green]")
260
- return True
261
-
262
- return asyncio.run(download_module())
263
-
264
- except Exception as e:
265
- console.print(Panel(f"[red]处理模块 {module_name} 文件失败: {e}[/red]", title="错误", border_style="red"))
266
- if os.path.exists(zip_path):
267
- try:
268
- os.remove(zip_path)
269
- except Exception as cleanup_error:
270
- console.print(f"[red]清理失败: {cleanup_error}[/red]")
271
- return False
272
-
273
- finally:
274
- if os.path.exists(zip_path):
275
- try:
276
- os.remove(zip_path)
277
- except Exception as cleanup_error:
278
- console.print(f"[red]清理失败: {cleanup_error}[/red]")
279
-
280
- def install_pip_dependencies(dependencies):
281
- if not dependencies:
282
- return True
283
-
284
- console.print("[cyan]正在安装pip依赖...[/cyan]")
285
- try:
286
- result = subprocess.run(
287
- [sys.executable, "-m", "pip", "install"] + dependencies,
288
- check=True,
289
- stdout=subprocess.PIPE,
290
- stderr=subprocess.PIPE
291
- )
292
- console.print(result.stdout.decode())
293
- return True
294
- except subprocess.CalledProcessError as e:
295
- console.print(Panel(f"[red]安装pip依赖失败: {e.stderr.decode()}[/red]", title="错误", border_style="red"))
296
- return False
297
-
298
- def install_module(module_name, force=False):
299
- # 显示安装摘要
300
- console.print(Panel.fit(
301
- f"[bold]准备安装模块:[/bold] [cyan]{module_name}[/cyan]",
302
- title="安装摘要",
303
- border_style="blue"
304
- ))
305
-
306
- last_update_time = env.get('last_origin_update_time', None)
307
- if last_update_time:
308
- from datetime import datetime, timedelta
309
- last_update = datetime.fromisoformat(last_update_time)
310
- if datetime.now() - last_update > timedelta(hours=0):
311
- console.print(Panel(
312
- "[yellow]距离上次源更新已超过72小时,源内可能有新模块或更新。[/yellow]",
313
- border_style="yellow"
314
- ))
315
- update_confirmation = Confirm.ask(
316
- "[yellow]是否在安装模块前更新源?[/yellow]",
317
- default=True
318
- )
319
- if update_confirmation:
320
- with console.status("[cyan]正在更新源...[/cyan]"):
321
- SourceManager().update_sources()
322
- env.set('last_origin_update_time', datetime.now().isoformat())
323
- console.print("[green]✓ 源更新完成[/green]")
324
-
325
- module_info = env.get_module(module_name)
326
- if module_info and not force:
327
- meta = module_info.get('info', {}).get('meta', {})
328
- console.print(Panel(
329
- f"[yellow]模块 {module_name} 已存在[/yellow]\n"
330
- f"版本: {meta.get('version', '未知')}\n"
331
- f"描述: {meta.get('description', '无描述')}",
332
- title="模块已存在",
333
- border_style="yellow"
334
- ))
335
- if not Confirm.ask("[yellow]是否要强制重新安装?[/yellow]", default=False):
336
- return
337
-
338
- providers = env.get('providers', {})
339
- if isinstance(providers, str):
340
- providers = json.loads(providers)
341
-
342
- module_info = []
343
- for provider, url in providers.items():
344
- module_key = f"{module_name}@{provider}"
345
- modules_data = env.get('modules', {})
346
- if isinstance(modules_data, str):
347
- modules_data = json.loads(modules_data)
348
-
349
- if module_key in modules_data:
350
- module_data = modules_data[module_key]
351
- console.print(f"[cyan]正在处理模块 {module_name} 源: {provider}...[/cyan]")
352
- console.print(f"[cyan]源地址: {url}[/cyan] | 信息: {module_data}")
353
- meta = module_data.get("meta", {})
354
- depsinfo = module_data.get("dependencies", {})
355
-
356
- module_info.append({
357
- 'provider': provider,
358
- 'url': url,
359
- 'path': module_data.get('path', ''),
360
- 'version': meta.get('version', '未知'),
361
- 'description': meta.get('description', '无描述'),
362
- 'author': meta.get('author', '未知'),
363
- 'dependencies': depsinfo.get("requires", []),
364
- 'optional_dependencies': depsinfo.get("optional", []),
365
- 'pip_dependencies': depsinfo.get("pip", [])
366
- })
367
-
368
- if not module_info:
369
- console.print(Panel(
370
- f"[red]未找到模块 {module_name}[/red]",
371
- title="错误",
372
- border_style="red"
373
- ))
374
- if providers:
375
- console.print("[cyan]当前可用源:[/cyan]")
376
- for provider in providers:
377
- console.print(f" - {provider}")
378
- return
379
-
380
- if len(module_info) > 1:
381
- console.print(f"[cyan]找到 {len(module_info)} 个源的 {module_name} 模块:[/cyan]")
382
- table = Table(title="可选模块源", show_header=True, header_style="bold magenta")
383
- table.add_column("编号", style="cyan")
384
- table.add_column("源", style="green")
385
- table.add_column("版本", style="blue")
386
- table.add_column("描述", style="white")
387
- table.add_column("作者", style="yellow")
388
- for i, info in enumerate(module_info):
389
- console.print(info)
390
- table.add_row(str(i+1), info['provider'], info.get('meta', {}).get('version', '未知'), info.get('description', {}).get('requires'), info.get('meta', {}).get('author', '未知'))
391
- console.print(table)
392
-
393
- while True:
394
- choice = Prompt.ask("请选择要安装的源 (输入编号)", default="1")
395
- if choice.isdigit() and 1 <= int(choice) <= len(module_info):
396
- selected_module = module_info[int(choice)-1]
397
- break
398
- else:
399
- console.print("[red]输入无效,请重新选择[/red]")
400
- else:
401
- selected_module = module_info[0]
402
-
403
- for dep in selected_module['dependencies']:
404
- console.print(f"[cyan]正在安装依赖模块 {dep}...[/cyan]")
405
- install_module(dep)
406
-
407
- third_party_deps = selected_module.get('pip_dependencies', [])
408
- if third_party_deps:
409
- console.print(f"[cyan]模块 {module_name} 需要以下pip依赖: {', '.join(third_party_deps)}[/cyan]")
410
- if not install_pip_dependencies(third_party_deps):
411
- console.print(f"[red]无法安装模块 {module_name} 的pip依赖,安装终止[/red]")
412
- return
413
-
414
- module_url = selected_module['url'] + selected_module['path']
415
- script_dir = os.path.dirname(os.path.abspath(__file__))
416
- module_dir = os.path.join(script_dir, 'modules', module_name)
417
- zip_path = os.path.join(script_dir, f"{module_name}.zip")
418
-
419
- if not extract_and_setup_module(
420
- module_name=module_name,
421
- module_url=module_url,
422
- zip_path=zip_path,
423
- module_dir=module_dir
424
- ):
425
- return
426
-
427
- env.set_module(module_name, {
428
- 'status': True,
429
- 'info': {
430
- 'meta': {
431
- 'version': selected_module['version'],
432
- 'description': selected_module['description'],
433
- 'author': selected_module['author'],
434
- 'pip_dependencies': selected_module['pip_dependencies']
435
- },
436
- 'dependencies': {
437
- 'requires': selected_module['dependencies'],
438
- 'optional': selected_module['optional_dependencies'],
439
- 'pip': selected_module['pip_dependencies']
440
- }
441
- }
442
- })
443
- console.print(f"[green]模块 {module_name} 安装成功[/green]")
444
-
445
- def uninstall_module(module_name):
446
- # 显示卸载摘要
447
- console.print(Panel.fit(
448
- f"[bold]准备卸载模块:[/bold] [cyan]{module_name}[/cyan]",
449
- title="卸载摘要",
450
- border_style="blue"
451
- ))
452
-
453
- module_info = env.get_module(module_name)
454
- if not module_info:
455
- console.print(Panel(
456
- f"[red]模块 {module_name} 不存在[/red]",
457
- title="错误",
458
- border_style="red"
459
- ))
460
- return
461
- meta = module_info.get('info', {}).get('meta', {})
462
- depsinfo = module_info.get('info', {}).get('dependencies', {})
463
-
464
- # 显示模块信息
465
- console.print(Panel(
466
- f"版本: {meta.get('version', '未知')}\n"
467
- f"描述: {meta.get('description', '无描述')}\n"
468
- f"pip依赖: {', '.join(depsinfo.get('pip', [])) or '无'}",
469
- title="模块信息",
470
- border_style="green"
471
- ))
472
-
473
- if not Confirm.ask("[red]确认要卸载此模块吗?[/red]", default=False):
474
- console.print("[yellow]卸载已取消[/yellow]")
475
- return
476
-
477
- script_dir = os.path.dirname(os.path.abspath(__file__))
478
- module_path = os.path.join(script_dir, 'modules', module_name)
479
-
480
- module_file_path = module_path + '.py'
481
- if os.path.exists(module_file_path):
482
- try:
483
- with console.status(f"[cyan]正在删除模块文件 {module_name}...[/cyan]"):
484
- os.remove(module_file_path)
485
- except Exception as e:
486
- console.print(Panel(
487
- f"[red]删除模块文件 {module_name} 时出错: {e}[/red]",
488
- title="错误",
489
- border_style="red"
490
- ))
491
- elif os.path.exists(module_path) and os.path.isdir(module_path):
492
- try:
493
- with console.status(f"[cyan]正在删除模块目录 {module_name}...[/cyan]"):
494
- shutil.rmtree(module_path)
495
- except Exception as e:
496
- console.print(Panel(
497
- f"[red]删除模块目录 {module_name} 时出错: {e}[/red]",
498
- title="错误",
499
- border_style="red"
500
- ))
501
- else:
502
- console.print(Panel(
503
- f"[red]模块 {module_name} 不存在[/red]",
504
- title="错误",
505
- border_style="red"
506
- ))
507
- return
508
-
509
- pip_dependencies = depsinfo.get('pip', [])
510
- if pip_dependencies:
511
- all_modules = env.get_all_modules()
512
- unused_pip_dependencies = []
513
-
514
- essential_packages = {'aiohttp', 'rich'}
515
-
516
- for dep in pip_dependencies:
517
- if dep in essential_packages:
518
- console.print(f"[yellow]跳过必要模块 {dep} 的卸载[/yellow]")
519
- continue
520
-
521
- is_dependency_used = False
522
- for name, info in all_modules.items():
523
- if name != module_name and dep in info.get('info', {}).get('dependencies', {}).get('pip', []):
524
- is_dependency_used = True
525
- break
526
- if not is_dependency_used:
527
- unused_pip_dependencies.append(dep)
528
-
529
- if unused_pip_dependencies:
530
- console.print(Panel(
531
- f"以下 pip 依赖不再被其他模块使用:\n{', '.join(unused_pip_dependencies)}",
532
- title="可卸载依赖",
533
- border_style="cyan"
534
- ))
535
- confirm = Confirm.ask("[yellow]是否卸载这些 pip 依赖?[/yellow]", default=False)
536
- if confirm:
537
- with console.status(f"[cyan]正在卸载 pip 依赖: {', '.join(unused_pip_dependencies)}[/cyan]"):
538
- try:
539
- subprocess.run(
540
- [sys.executable, "-m", "pip", "uninstall", "-y"] + unused_pip_dependencies,
541
- check=True,
542
- stdout=subprocess.PIPE,
543
- stderr=subprocess.PIPE
544
- )
545
- console.print(Panel(
546
- f"[green]成功卸载 pip 依赖: {', '.join(unused_pip_dependencies)}[/green]",
547
- border_style="green"
548
- ))
549
- except subprocess.CalledProcessError as e:
550
- console.print(Panel(
551
- f"[red]卸载 pip 依赖失败: {e.stderr.decode()}[/red]",
552
- title="错误",
553
- border_style="red"
554
- ))
555
-
556
- if env.remove_module(module_name):
557
- console.print(Panel(
558
- f"[green]✓ 模块 {module_name} 已成功卸载[/green]",
559
- border_style="green"
560
- ))
561
- else:
562
- console.print(Panel(
563
- f"[red]模块 {module_name} 不存在[/red]",
564
- title="错误",
565
- border_style="red"
566
- ))
567
- def upgrade_all_modules(force=False):
568
- all_modules = env.get_all_modules()
569
- if not all_modules:
570
- console.print("[yellow]未找到任何模块,无法更新[/yellow]")
571
- return
572
-
573
- providers = env.get('providers', {})
574
- if isinstance(providers, str):
575
- providers = json.loads(providers)
576
-
577
- modules_data = env.get('modules', {})
578
- if isinstance(modules_data, str):
579
- modules_data = json.loads(modules_data)
580
-
581
- updates_available = []
582
- for module_name, module_info in all_modules.items():
583
- local_version = module_info.get('info', []).get('meta', []).get('version', '0.0.0')
584
- for provider, url in providers.items():
585
- module_key = f"{module_name}@{provider}"
586
- if module_key in modules_data:
587
- remote_module = modules_data[module_key]
588
- remote_version = remote_module.get('meta', {}).get('version', '1.14.514')
589
- if remote_version > local_version:
590
- updates_available.append({
591
- 'name': module_name,
592
- 'local_version': local_version,
593
- 'remote_version': remote_version,
594
- 'provider': provider,
595
- 'url': url,
596
- 'path': remote_module.get('path', ''),
597
- })
598
-
599
- if not updates_available:
600
- console.print("[green]所有模块已是最新版本,无需更新[/green]")
601
- return
602
-
603
- console.print("\n[cyan]以下模块有可用更新:[/cyan]")
604
- table = Table(title="可用更新", show_header=True, header_style="bold magenta")
605
- table.add_column("模块", style="cyan")
606
- table.add_column("当前版本", style="yellow")
607
- table.add_column("最新版本", style="green")
608
- table.add_column("源", style="blue")
609
- for update in updates_available:
610
- table.add_row(update['name'], update['local_version'], update['remote_version'], update['provider'])
611
- console.print(table)
612
-
613
- if not force:
614
- confirm = Confirm.ask("[yellow]警告:更新模块可能会导致兼容性问题,请在更新前查看插件作者的相关声明。\n是否继续?[/yellow]", default=False)
615
- if not confirm:
616
- console.print("[yellow]更新已取消[/yellow]")
617
- return
618
-
619
- for update in updates_available:
620
- console.print(f"[cyan]正在更新模块 {update['name']}...[/cyan]")
621
- module_url = update['url'] + update['path']
622
- script_dir = os.path.dirname(os.path.abspath(__file__))
623
- module_dir = os.path.join(script_dir, 'modules', update['name'])
624
- zip_path = os.path.join(script_dir, f"{update['name']}.zip")
625
-
626
- if not extract_and_setup_module(
627
- module_name=update['name'],
628
- module_url=module_url,
629
- zip_path=zip_path,
630
- module_dir=module_dir
631
- ):
632
- continue
633
-
634
- all_modules[update['name']]['info']['version'] = update['remote_version']
635
- env.set_all_modules(all_modules)
636
- console.print(f"[green]模块 {update['name']} 已更新至版本 {update['remote_version']}[/green]")
637
-
638
- def list_modules(module_name=None):
639
- all_modules = env.get_all_modules()
640
- if not all_modules:
641
- console.print(Panel(
642
- "[yellow]未在数据库中发现注册模块,正在初始化模块列表...[/yellow]",
643
- border_style="yellow"
644
- ))
645
- from . import init as init_module
646
- init_module()
647
- all_modules = env.get_all_modules()
648
-
649
- if not all_modules:
650
- console.print(Panel(
651
- "[red]未找到任何模块[/red]",
652
- title="错误",
653
- border_style="red"
654
- ))
655
- return
656
-
657
- # 显示模块总数
658
- console.print(Panel.fit(
659
- f"[bold]找到 {len(all_modules)} 个模块[/bold]",
660
- border_style="blue"
661
- ))
662
-
663
- table = Table(
664
- title="模块列表",
665
- show_header=True,
666
- header_style="bold magenta",
667
- expand=True,
668
- box=box.ROUNDED
669
- )
670
- table.add_column("模块名称", style="cyan", min_width=20)
671
- table.add_column("状态", style="green", justify="center")
672
- table.add_column("版本", style="blue", justify="center")
673
- table.add_column("描述", style="white", min_width=30)
674
- table.add_column("依赖", style="yellow", min_width=15)
675
- table.add_column("可选依赖", style="magenta", min_width=15)
676
- table.add_column("pip依赖", style="cyan", min_width=20)
677
-
678
- for name, info in all_modules.items():
679
- status = "[green]✓[/green]" if info.get("status", True) else "[red]✗[/red]"
680
- meta = info.get('info', {}).get('meta', {})
681
- depsinfo = info.get('info', {}).get('dependencies', {})
682
- optional_deps = depsinfo.get('optional', [])
683
- available_optional_deps = []
684
- missing_optional_deps = []
685
-
686
- if optional_deps:
687
- for dep in optional_deps:
688
- if isinstance(dep, list):
689
- # 处理嵌套列表
690
- available_deps = [d for d in dep if d in all_modules]
691
- if available_deps:
692
- available_optional_deps.extend(available_deps)
693
- else:
694
- missing_optional_deps.extend(dep)
695
- elif dep in all_modules:
696
- # 单个字符串依赖
697
- available_optional_deps.append(dep)
698
- else:
699
- missing_optional_deps.append(dep)
700
-
701
- optional_dependencies = (
702
- f"[green]可用: {', '.join(available_optional_deps)}[/green]\n"
703
- f"[red]缺失: {', '.join(missing_optional_deps)}[/red]"
704
- if missing_optional_deps
705
- else ', '.join(available_optional_deps) or '无'
706
- )
707
- else:
708
- optional_dependencies = '无'
709
-
710
- dependencies = '\n'.join(depsinfo.get('requires', [])) or '无'
711
- pip_dependencies = '\n'.join(depsinfo.get('pip', [])) or '无'
712
-
713
- table.add_row(
714
- f"[bold]{name}[/bold]",
715
- status,
716
- meta.get('version', '未知'),
717
- meta.get('description', '无描述'),
718
- dependencies,
719
- optional_dependencies,
720
- pip_dependencies
721
- )
722
-
723
- console.print(table)
724
-
725
- # 显示模块状态统计
726
- enabled_count = sum(1 for m in all_modules.values() if m.get("status", True))
727
- disabled_count = len(all_modules) - enabled_count
728
- console.print(Panel(
729
- f"[green]已启用: {enabled_count}[/green] [red]已禁用: {disabled_count}[/red]",
730
- title="模块状态统计",
731
- border_style="blue"
732
- ))
733
-
734
- def main():
735
- parser = argparse.ArgumentParser(
736
- description="ErisPulse 命令行工具",
737
- prog="ep"
738
- )
739
- subparsers = parser.add_subparsers(dest='command', help='可用命令')
740
-
741
- # 添加子命令解析器(与原代码一致)
742
- enable_parser = subparsers.add_parser('enable', help='启用指定模块')
743
- enable_parser.add_argument('module_names', nargs='+', help='要启用的模块名称(支持多个模块,用空格分隔)')
744
- enable_parser.add_argument('--init', action='store_true', help='在启用模块前初始化模块数据库')
745
-
746
- disable_parser = subparsers.add_parser('disable', help='禁用指定模块')
747
- disable_parser.add_argument('module_names', nargs='+', help='要禁用的模块名称(支持多个模块,用空格分隔)')
748
- disable_parser.add_argument('--init', action='store_true', help='在禁用模块前初始化模块数据库')
749
-
750
- list_parser = subparsers.add_parser('list', help='列出所有模块信息')
751
- list_parser.add_argument('--module', '-m', type=str, help='指定要展示的模块名称')
752
-
753
- update_parser = subparsers.add_parser('update', help='更新模块列表')
754
-
755
- upgrade_parser = subparsers.add_parser('upgrade', help='升级模块列表')
756
- upgrade_parser.add_argument('--force', action='store_true', help='跳过二次确认,强制更新')
757
-
758
- uninstall_parser = subparsers.add_parser('uninstall', help='删除指定模块')
759
- uninstall_parser.add_argument('module_names', nargs='+', help='要卸载的模块名称(支持多个模块,用空格分隔)')
760
-
761
- install_parser = subparsers.add_parser('install', help='安装指定模块(支持多个模块,用空格分隔)')
762
- install_parser.add_argument('module_name', nargs='+', help='要安装的模块名称(支持多个模块,用空格分隔)')
763
- install_parser.add_argument('--force', action='store_true', help='强制重新安装模块')
764
- install_parser.add_argument('--init', action='store_true', help='在安装模块前初始化模块数据库')
765
-
766
- origin_parser = subparsers.add_parser('origin', help='管理模块源')
767
- origin_subparsers = origin_parser.add_subparsers(dest='origin_command', help='源管理命令')
768
-
769
- add_origin_parser = origin_subparsers.add_parser('add', help='添加模块源')
770
- add_origin_parser.add_argument('url', type=str, help='要添加的模块源URL')
771
-
772
- list_origin_parser = origin_subparsers.add_parser('list', help='列出所有模块源')
773
-
774
- del_origin_parser = origin_subparsers.add_parser('del', help='删除模块源')
775
- del_origin_parser.add_argument('url', type=str, help='要删除的模块源URL')
776
-
777
- args = parser.parse_args()
778
- source_manager = SourceManager()
779
-
780
- # 初始化模块数据库
781
- if hasattr(args, 'init') and args.init:
782
- console.print("[yellow]正在初始化模块列表...[/yellow]")
783
- from . import init as init_module
784
- init_module()
785
-
786
- if args.command == 'enable':
787
- for module_name in args.module_names:
788
- module_name = module_name.strip()
789
- if not module_name:
790
- continue
791
- if '*' in module_name or '?' in module_name:
792
- console.print(f"[cyan]正在匹配模块模式: {module_name}...[/cyan]")
793
- all_modules = env.get_all_modules()
794
- if not all_modules:
795
- console.print(Panel("[red]未找到任何模块,请先更新源或检查配置[/red]", title="错误", border_style="red"))
796
- continue
797
- matched_modules = [name for name in all_modules.keys() if fnmatch.fnmatch(name, module_name)]
798
- if not matched_modules:
799
- console.print(Panel(f"[red]未找到匹配模块模式 {module_name} 的模块[/red]", title="错误", border_style="red"))
800
- continue
801
- console.print(f"[green]找到 {len(matched_modules)} 个匹配模块:[/green]")
802
- for i, matched_module in enumerate(matched_modules, start=1):
803
- console.print(f" {i}. {matched_module}")
804
- confirm = Confirm.ask("[yellow]是否启用所有匹配模块?[/yellow]", default=True)
805
- if not confirm:
806
- console.print("[yellow]操作已取消[/yellow]")
807
- continue
808
- for matched_module in matched_modules:
809
- enable_module(matched_module)
810
- else:
811
- enable_module(module_name)
812
- elif args.command == 'disable':
813
- for module_name in args.module_names:
814
- module_name = module_name.strip()
815
- if not module_name:
816
- continue
817
- if '*' in module_name or '?' in module_name:
818
- console.print(f"[cyan]正在匹配模块模式: {module_name}...[/cyan]")
819
- all_modules = env.get_all_modules()
820
- if not all_modules:
821
- console.print(Panel("[red]未找到任何模块,请先更新源或检查配置[/red]", title="错误", border_style="red"))
822
- continue
823
- matched_modules = [name for name in all_modules.keys() if fnmatch.fnmatch(name, module_name)]
824
- if not matched_modules:
825
- console.print(Panel(f"[red]未找到匹配模块模式 {module_name} 的模块[/red]", title="错误", border_style="red"))
826
- continue
827
- console.print(f"[green]找到 {len(matched_modules)} 个匹配模块:[/green]")
828
- for i, matched_module in enumerate(matched_modules, start=1):
829
- console.print(f" {i}. {matched_module}")
830
- confirm = Confirm.ask("[yellow]是否禁用所有匹配模块?[/yellow]", default=True)
831
- if not confirm:
832
- console.print("[yellow]操作已取消[/yellow]")
833
- continue
834
- for matched_module in matched_modules:
835
- disable_module(matched_module)
836
- else:
837
- disable_module(module_name)
838
- elif args.command == 'list':
839
- list_modules(args.module)
840
- elif args.command == 'uninstall':
841
- for module_name in args.module_names:
842
- module_name = module_name.strip()
843
- if not module_name:
844
- continue
845
- if '*' in module_name or '?' in module_name:
846
- console.print(f"[cyan]正在匹配模块模式: {module_name}...[/cyan]")
847
- all_modules = env.get_all_modules()
848
- if not all_modules:
849
- console.print(Panel("[red]未找到任何模块,请先更新源或检查配置[/red]", title="错误", border_style="red"))
850
- continue
851
- matched_modules = [name for name in all_modules.keys() if fnmatch.fnmatch(name, module_name)]
852
- if not matched_modules:
853
- console.print(Panel(f"[red]未找到匹配模块模式 {module_name} 的模块[/red]", title="错误", border_style="red"))
854
- continue
855
- console.print(f"[green]找到 {len(matched_modules)} 个匹配模块:[/green]")
856
- for i, matched_module in enumerate(matched_modules, start=1):
857
- console.print(f" {i}. {matched_module}")
858
- confirm = Confirm.ask("[yellow]是否卸载所有匹配模块?[/yellow]", default=True)
859
- if not confirm:
860
- console.print("[yellow]操作已取消[/yellow]")
861
- continue
862
- for matched_module in matched_modules:
863
- uninstall_module(matched_module)
864
- else:
865
- uninstall_module(module_name)
866
- elif args.command == 'install':
867
- for module_name in args.module_name:
868
- module_name = module_name.strip()
869
- if not module_name:
870
- continue
871
- if '*' in module_name or '?' in module_name:
872
- console.print(f"[cyan]正在匹配模块模式: {module_name}...[/cyan]")
873
- all_modules = env.get_all_modules()
874
- if not all_modules:
875
- console.print(Panel(
876
- "[red]未找到任何模块,请先更新源或检查配置[/red]",
877
- title="错误",
878
- border_style="red"
879
- ))
880
- continue
881
-
882
- matched_modules = [
883
- name for name in all_modules.keys() if fnmatch.fnmatch(name, module_name)
884
- ]
885
-
886
- if not matched_modules:
887
- console.print(Panel(
888
- f"[red]未找到匹配模块模式 {module_name} 的模块[/red]",
889
- title="错误",
890
- border_style="red"
891
- ))
892
- continue
893
-
894
- console.print(f"[green]找到 {len(matched_modules)} 个匹配模块:[/green]")
895
- for i, matched_module in enumerate(matched_modules, start=1):
896
- console.print(f" {i}. {matched_module}")
897
-
898
- confirm = Confirm.ask("[yellow]是否安装所有匹配模块?[/yellow]", default=True)
899
- if not confirm:
900
- console.print("[yellow]安装已取消[/yellow]")
901
- continue
902
-
903
- for matched_module in matched_modules:
904
- install_module(matched_module, args.force)
905
- else:
906
- install_module(module_name, args.force)
907
- elif args.command == 'update':
908
- SourceManager().update_sources()
909
- elif args.command == 'upgrade':
910
- upgrade_all_modules(args.force)
911
- elif args.command == 'origin':
912
- if args.origin_command == 'add':
913
- success = source_manager.add_source(args.url)
914
- if success:
915
- update_confirmation = Confirm.ask(
916
- "[yellow]源已添加,是否立即更新源以获取最新模块信息?[/yellow]",
917
- default=True
918
- )
919
- if update_confirmation:
920
- source_manager.update_sources()
921
- elif args.origin_command == 'list':
922
- source_manager.list_sources()
923
- elif args.origin_command == 'del':
924
- source_manager.del_source(args.url)
925
- else:
926
- origin_parser.print_help()
927
- else:
928
- parser.print_help()
929
-
930
- if __name__ == "__main__":
931
- main()
1
+ import argparse
2
+ import os
3
+ import sys
4
+ import shutil
5
+ import aiohttp
6
+ import zipfile
7
+ import fnmatch
8
+ import asyncio
9
+ import subprocess
10
+ import json
11
+ from .db import env
12
+
13
+ def print_panel(msg, title=None, border_style=None):
14
+ print("=" * 60)
15
+ if title:
16
+ print(f"[{title}]")
17
+ print(msg)
18
+ print("=" * 60)
19
+
20
+ def print_table(headers, rows, title=None):
21
+ if title:
22
+ print(f"== {title} ==")
23
+ col_widths = [len(h) for h in headers]
24
+ for row in rows:
25
+ for i, cell in enumerate(row):
26
+ col_widths[i] = max(col_widths[i], len(str(cell)))
27
+ fmt = " | ".join("{:<" + str(w) + "}" for w in col_widths)
28
+ print(fmt.format(*headers))
29
+ print("-" * (sum(col_widths) + 3 * (len(headers) - 1)))
30
+ for row in rows:
31
+ print(fmt.format(*row))
32
+
33
+ def confirm(msg, default=False):
34
+ yes = {'y', 'yes', ''}
35
+ no = {'n', 'no'}
36
+ prompt = f"{msg} [{'Y/n' if default else 'y/N'}]: "
37
+ while True:
38
+ ans = input(prompt).strip().lower()
39
+ if not ans:
40
+ return default
41
+ if ans in yes:
42
+ return True
43
+ if ans in no:
44
+ return False
45
+
46
+ def ask(msg, choices=None, default=None):
47
+ prompt = f"{msg}"
48
+ if choices:
49
+ prompt += f" ({'/'.join(choices)})"
50
+ if default:
51
+ prompt += f" [default: {default}]"
52
+ prompt += ": "
53
+ while True:
54
+ ans = input(prompt).strip()
55
+ if not ans and default:
56
+ return default
57
+ if not choices or ans in choices:
58
+ return ans
59
+
60
+ class SourceManager:
61
+ def __init__(self):
62
+ self._init_sources()
63
+
64
+ def _init_sources(self):
65
+ if not env.get('origins'):
66
+ env.set('origins', [])
67
+
68
+ async def _validate_url(self, url):
69
+ if not url.startswith(('http://', 'https://')):
70
+ protocol = ask("未指定协议,请输入使用的协议", choices=['http', 'https'], default="https")
71
+ url = f"{protocol}://{url}"
72
+ if not url.endswith('.json'):
73
+ url = f"{url}/map.json"
74
+ try:
75
+ async with aiohttp.ClientSession() as session:
76
+ async with session.get(url) as response:
77
+ response.raise_for_status()
78
+ if response.headers.get('Content-Type', '').startswith('application/json'):
79
+ return url
80
+ else:
81
+ print_panel(f"源 {url} 返回的内容不是有效的 JSON 格式", "错误")
82
+ return None
83
+ except Exception as e:
84
+ print_panel(f"访问源 {url} 失败: {e}", "错误")
85
+ return None
86
+
87
+ def add_source(self, value):
88
+ validated_url = asyncio.run(self._validate_url(value))
89
+ if not validated_url:
90
+ print_panel("提供的源不是一个有效源,请检查后重试", "错误")
91
+ return False
92
+ origins = env.get('origins')
93
+ if validated_url not in origins:
94
+ origins.append(validated_url)
95
+ env.set('origins', origins)
96
+ print_panel(f" {validated_url} 已成功添加", "成功")
97
+ return True
98
+ else:
99
+ print_panel(f"源 {validated_url} 已存在,无需重复添加", "提示")
100
+ return False
101
+
102
+ def update_sources(self):
103
+ origins = env.get('origins')
104
+ providers = {}
105
+ modules = {}
106
+ module_alias = {}
107
+ table_rows = []
108
+ async def fetch_source_data():
109
+ async with aiohttp.ClientSession() as session:
110
+ for origin in origins:
111
+ print(f"正在获取 {origin}...")
112
+ try:
113
+ async with session.get(origin) as response:
114
+ response.raise_for_status()
115
+ if response.headers.get('Content-Type', '').startswith('application/json'):
116
+ content = await response.json()
117
+ providers[content["name"]] = content["base"]
118
+ for module in content["modules"].keys():
119
+ module_content = content["modules"][module]
120
+ modules[f'{module}@{content["name"]}'] = module_content
121
+ module_origin_name = module_content["path"]
122
+ module_alias_name = module
123
+ module_alias[f'{module_origin_name}@{content["name"]}'] = module_alias_name
124
+ table_rows.append([
125
+ content['name'],
126
+ module,
127
+ f"{providers[content['name']]}{module_origin_name}"
128
+ ])
129
+ else:
130
+ print_panel(f"源 {origin} 返回的内容不是有效的 JSON 格式", "错误")
131
+ except Exception as e:
132
+ print_panel(f"获取 {origin} 时出错: {e}", "错误")
133
+ asyncio.run(fetch_source_data())
134
+ print_table(["源", "模块", "地址"], table_rows, "源更新状态")
135
+ from datetime import datetime
136
+ env.set('providers', providers)
137
+ env.set('modules', modules)
138
+ env.set('module_alias', module_alias)
139
+ env.set('last_origin_update_time', datetime.now().isoformat())
140
+ print_panel("源更新完成", "成功")
141
+
142
+ def list_sources(self):
143
+ origins = env.get('origins')
144
+ if not origins:
145
+ print_panel("当前没有配置任何源", "提示")
146
+ return
147
+ rows = [[str(idx), origin] for idx, origin in enumerate(origins, 1)]
148
+ print_table(["序号", "源地址"], rows, "已配置的源")
149
+
150
+ def del_source(self, value):
151
+ origins = env.get('origins')
152
+ if value in origins:
153
+ origins.remove(value)
154
+ env.set('origins', origins)
155
+ print_panel(f"源 {value} 已成功删除", "成功")
156
+ else:
157
+ print_panel(f"源 {value} 不存在", "错误")
158
+
159
+ def enable_module(module_name):
160
+ module_info = env.get_module(module_name)
161
+ if module_info:
162
+ env.set_module_status(module_name, True)
163
+ print_panel(f"✓ 模块 {module_name} 已成功启用", "成功")
164
+ else:
165
+ print_panel(f"模块 {module_name} 不存在", "错误")
166
+
167
+ def disable_module(module_name):
168
+ module_info = env.get_module(module_name)
169
+ if module_info:
170
+ env.set_module_status(module_name, False)
171
+ print_panel(f"✓ 模块 {module_name} 已成功禁用", "成功")
172
+ else:
173
+ print_panel(f"模块 {module_name} 不存在", "错误")
174
+
175
+ async def fetch_url(session, url):
176
+ try:
177
+ async with session.get(url) as response:
178
+ response.raise_for_status()
179
+ return await response.read()
180
+ except Exception as e:
181
+ print(f"请求失败: {e}")
182
+ return None
183
+
184
+ def extract_and_setup_module(module_name, module_url, zip_path, module_dir):
185
+ try:
186
+ print(f"正在从 {module_url} 下载模块...")
187
+ async def download_module():
188
+ async with aiohttp.ClientSession() as session:
189
+ content = await fetch_url(session, module_url)
190
+ if content is None:
191
+ return False
192
+ with open(zip_path, 'wb') as zip_file:
193
+ zip_file.write(content)
194
+ if not os.path.exists(module_dir):
195
+ os.makedirs(module_dir)
196
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
197
+ zip_ref.extractall(module_dir)
198
+ init_file_path = os.path.join(module_dir, '__init__.py')
199
+ if not os.path.exists(init_file_path):
200
+ sub_module_dir = os.path.join(module_dir, module_name)
201
+ m_sub_module_dir = os.path.join(module_dir, f"m_{module_name}")
202
+ for sub_dir in [sub_module_dir, m_sub_module_dir]:
203
+ if os.path.exists(sub_dir) and os.path.isdir(sub_dir):
204
+ for item in os.listdir(sub_dir):
205
+ source_item = os.path.join(sub_dir, item)
206
+ target_item = os.path.join(module_dir, item)
207
+ if os.path.exists(target_item):
208
+ os.remove(target_item)
209
+ shutil.move(source_item, module_dir)
210
+ os.rmdir(sub_dir)
211
+ print(f"模块 {module_name} 文件已成功解压并设置")
212
+ return True
213
+ return asyncio.run(download_module())
214
+ except Exception as e:
215
+ print_panel(f"处理模块 {module_name} 文件失败: {e}", "错误")
216
+ if os.path.exists(zip_path):
217
+ try:
218
+ os.remove(zip_path)
219
+ except Exception as cleanup_error:
220
+ print(f"清理失败: {cleanup_error}")
221
+ return False
222
+ finally:
223
+ if os.path.exists(zip_path):
224
+ try:
225
+ os.remove(zip_path)
226
+ except Exception as cleanup_error:
227
+ print(f"清理失败: {cleanup_error}")
228
+
229
+ def install_pip_dependencies(dependencies):
230
+ if not dependencies:
231
+ return True
232
+ print("正在安装pip依赖...")
233
+ try:
234
+ result = subprocess.run(
235
+ [sys.executable, "-m", "pip", "install"] + dependencies,
236
+ check=True,
237
+ stdout=subprocess.PIPE,
238
+ stderr=subprocess.PIPE
239
+ )
240
+ print(result.stdout.decode())
241
+ return True
242
+ except subprocess.CalledProcessError as e:
243
+ print_panel(f"安装pip依赖失败: {e.stderr.decode()}", "错误")
244
+ return False
245
+
246
+ def install_module(module_name, force=False):
247
+ print_panel(f"准备安装模块: {module_name}", "安装摘要")
248
+ last_update_time = env.get('last_origin_update_time', None)
249
+ if last_update_time:
250
+ from datetime import datetime, timedelta
251
+ last_update = datetime.fromisoformat(last_update_time)
252
+ if datetime.now() - last_update > timedelta(hours=720):
253
+ print_panel("距离上次源更新已超过30天,源内可能有新模块或更新。", "提示")
254
+ if confirm("是否在安装模块前更新源?", default=True):
255
+ SourceManager().update_sources()
256
+ env.set('last_origin_update_time', datetime.now().isoformat())
257
+ print("✓ 源更新完成")
258
+ module_info = env.get_module(module_name)
259
+ if module_info and not force:
260
+ meta = module_info.get('info', {}).get('meta', {})
261
+ print_panel(
262
+ f"模块 {module_name} 已存在\n版本: {meta.get('version', '未知')}\n描述: {meta.get('description', '无描述')}",
263
+ "模块已存在"
264
+ )
265
+ if not confirm("是否要强制重新安装?", default=False):
266
+ return
267
+ providers = env.get('providers', {})
268
+ if isinstance(providers, str):
269
+ providers = json.loads(providers)
270
+ module_info_list = []
271
+ for provider, url in providers.items():
272
+ module_key = f"{module_name}@{provider}"
273
+ modules_data = env.get('modules', {})
274
+ if isinstance(modules_data, str):
275
+ modules_data = json.loads(modules_data)
276
+ if module_key in modules_data:
277
+ module_data = modules_data[module_key]
278
+ meta = module_data.get("meta", {})
279
+ depsinfo = module_data.get("dependencies", {})
280
+ module_info_list.append({
281
+ 'provider': provider,
282
+ 'url': url,
283
+ 'path': module_data.get('path', ''),
284
+ 'version': meta.get('version', '未知'),
285
+ 'description': meta.get('description', '无描述'),
286
+ 'author': meta.get('author', '未知'),
287
+ 'dependencies': depsinfo.get("requires", []),
288
+ 'optional_dependencies': depsinfo.get("optional", []),
289
+ 'pip_dependencies': depsinfo.get("pip", [])
290
+ })
291
+ if not module_info_list:
292
+ print_panel(f"未找到模块 {module_name}", "错误")
293
+ if providers:
294
+ print("当前可用源:")
295
+ for provider in providers:
296
+ print(f" - {provider}")
297
+ return
298
+ if len(module_info_list) > 1:
299
+ print(f"找到 {len(module_info_list)} 个源的 {module_name} 模块:")
300
+ rows = []
301
+ for i, info in enumerate(module_info_list):
302
+ rows.append([
303
+ str(i+1), info['provider'], info['version'], info['description'], info['author']
304
+ ])
305
+ print_table(["编号", "源", "版本", "描述", "作者"], rows, "可选模块源")
306
+ while True:
307
+ choice = ask("请选择要安装的源 (输入编号)", default="1")
308
+ if choice.isdigit() and 1 <= int(choice) <= len(module_info_list):
309
+ selected_module = module_info_list[int(choice)-1]
310
+ break
311
+ else:
312
+ print("输入无效,请重新选择")
313
+ else:
314
+ selected_module = module_info_list[0]
315
+ for dep in selected_module['dependencies']:
316
+ print(f"正在安装依赖模块 {dep}...")
317
+ install_module(dep)
318
+ third_party_deps = selected_module.get('pip_dependencies', [])
319
+ if third_party_deps:
320
+ print(f"模块 {module_name} 需要以下pip依赖: {', '.join(third_party_deps)}")
321
+ if not install_pip_dependencies(third_party_deps):
322
+ print(f"无法安装模块 {module_name} 的pip依赖,安装终止")
323
+ return
324
+ module_url = selected_module['url'] + selected_module['path']
325
+ script_dir = os.path.dirname(os.path.abspath(__file__))
326
+ module_dir = os.path.join(script_dir, 'modules', module_name)
327
+ zip_path = os.path.join(script_dir, f"{module_name}.zip")
328
+ if not extract_and_setup_module(
329
+ module_name=module_name,
330
+ module_url=module_url,
331
+ zip_path=zip_path,
332
+ module_dir=module_dir
333
+ ):
334
+ return
335
+ env.set_module(module_name, {
336
+ 'status': True,
337
+ 'info': {
338
+ 'meta': {
339
+ 'version': selected_module['version'],
340
+ 'description': selected_module['description'],
341
+ 'author': selected_module['author'],
342
+ 'pip_dependencies': selected_module['pip_dependencies']
343
+ },
344
+ 'dependencies': {
345
+ 'requires': selected_module['dependencies'],
346
+ 'optional': selected_module['optional_dependencies'],
347
+ 'pip': selected_module['pip_dependencies']
348
+ }
349
+ }
350
+ })
351
+ print(f"模块 {module_name} 安装成功")
352
+
353
+ def uninstall_module(module_name):
354
+ print_panel(f"准备卸载模块: {module_name}", "卸载摘要")
355
+ module_info = env.get_module(module_name)
356
+ if not module_info:
357
+ print_panel(f"模块 {module_name} 不存在", "错误")
358
+ return
359
+ meta = module_info.get('info', {}).get('meta', {})
360
+ depsinfo = module_info.get('info', {}).get('dependencies', {})
361
+ print_panel(
362
+ f"版本: {meta.get('version', '未知')}\n描述: {meta.get('description', '无描述')}\npip依赖: {', '.join(depsinfo.get('pip', [])) or '无'}",
363
+ "模块信息"
364
+ )
365
+ if not confirm("确认要卸载此模块吗?", default=False):
366
+ print("卸载已取消")
367
+ return
368
+ script_dir = os.path.dirname(os.path.abspath(__file__))
369
+ module_path = os.path.join(script_dir, 'modules', module_name)
370
+ module_file_path = module_path + '.py'
371
+ if os.path.exists(module_file_path):
372
+ try:
373
+ os.remove(module_file_path)
374
+ except Exception as e:
375
+ print_panel(f"删除模块文件 {module_name} 时出错: {e}", "错误")
376
+ elif os.path.exists(module_path) and os.path.isdir(module_path):
377
+ try:
378
+ shutil.rmtree(module_path)
379
+ except Exception as e:
380
+ print_panel(f"删除模块目录 {module_name} 时出错: {e}", "错误")
381
+ else:
382
+ print_panel(f"模块 {module_name} 不存在", "错误")
383
+ return
384
+ pip_dependencies = depsinfo.get('pip', [])
385
+ if pip_dependencies:
386
+ all_modules = env.get_all_modules()
387
+ unused_pip_dependencies = []
388
+ essential_packages = {'aiohttp'}
389
+ for dep in pip_dependencies:
390
+ if dep in essential_packages:
391
+ print(f"跳过必要模块 {dep} 的卸载")
392
+ continue
393
+ is_dependency_used = False
394
+ for name, info in all_modules.items():
395
+ if name != module_name and dep in info.get('info', {}).get('dependencies', {}).get('pip', []):
396
+ is_dependency_used = True
397
+ break
398
+ if not is_dependency_used:
399
+ unused_pip_dependencies.append(dep)
400
+ if unused_pip_dependencies:
401
+ print_panel(
402
+ f"以下 pip 依赖不再被其他模块使用:\n{', '.join(unused_pip_dependencies)}",
403
+ "可卸载依赖"
404
+ )
405
+ if confirm("是否卸载这些 pip 依赖?", default=False):
406
+ try:
407
+ subprocess.run(
408
+ [sys.executable, "-m", "pip", "uninstall", "-y"] + unused_pip_dependencies,
409
+ check=True,
410
+ stdout=subprocess.PIPE,
411
+ stderr=subprocess.PIPE
412
+ )
413
+ print_panel(
414
+ f"成功卸载 pip 依赖: {', '.join(unused_pip_dependencies)}",
415
+ "成功"
416
+ )
417
+ except subprocess.CalledProcessError as e:
418
+ print_panel(
419
+ f"卸载 pip 依赖失败: {e.stderr.decode()}",
420
+ "错误"
421
+ )
422
+ if env.remove_module(module_name):
423
+ print_panel(f"✓ 模块 {module_name} 已成功卸载", "成功")
424
+ else:
425
+ print_panel(f"模块 {module_name} 不存在", "错误")
426
+
427
+ def upgrade_all_modules(force=False):
428
+ all_modules = env.get_all_modules()
429
+ if not all_modules:
430
+ print("未找到任何模块,无法更新")
431
+ return
432
+ providers = env.get('providers', {})
433
+ if isinstance(providers, str):
434
+ providers = json.loads(providers)
435
+ modules_data = env.get('modules', {})
436
+ if isinstance(modules_data, str):
437
+ modules_data = json.loads(modules_data)
438
+ updates_available = []
439
+ for module_name, module_info in all_modules.items():
440
+ local_version = module_info.get('info', {}).get('meta', {}).get('version', '0.0.0')
441
+ for provider, url in providers.items():
442
+ module_key = f"{module_name}@{provider}"
443
+ if module_key in modules_data:
444
+ remote_module = modules_data[module_key]
445
+ remote_version = remote_module.get('meta', {}).get('version', '1.14.514')
446
+ if remote_version > local_version:
447
+ updates_available.append({
448
+ 'name': module_name,
449
+ 'local_version': local_version,
450
+ 'remote_version': remote_version,
451
+ 'provider': provider,
452
+ 'url': url,
453
+ 'path': remote_module.get('path', ''),
454
+ })
455
+ if not updates_available:
456
+ print("所有模块已是最新版本,无需更新")
457
+ return
458
+ print("\n以下模块有可用更新:")
459
+ rows = []
460
+ for update in updates_available:
461
+ rows.append([update['name'], update['local_version'], update['remote_version'], update['provider']])
462
+ print_table(["模块", "当前版本", "最新版本", "源"], rows, "可用更新")
463
+ if not force:
464
+ if not confirm("警告:更新模块可能会导致兼容性问题,请在更新前查看插件作者的相关声明。\n是否继续?", default=False):
465
+ print("更新已取消")
466
+ return
467
+ for update in updates_available:
468
+ print(f"正在更新模块 {update['name']}...")
469
+ module_url = update['url'] + update['path']
470
+ script_dir = os.path.dirname(os.path.abspath(__file__))
471
+ module_dir = os.path.join(script_dir, 'modules', update['name'])
472
+ zip_path = os.path.join(script_dir, f"{update['name']}.zip")
473
+ if not extract_and_setup_module(
474
+ module_name=update['name'],
475
+ module_url=module_url,
476
+ zip_path=zip_path,
477
+ module_dir=module_dir
478
+ ):
479
+ continue
480
+ all_modules[update['name']]['info']['version'] = update['remote_version']
481
+ env.set_all_modules(all_modules)
482
+ print(f"模块 {update['name']} 已更新至版本 {update['remote_version']}")
483
+
484
+ def list_modules(module_name=None):
485
+ all_modules = env.get_all_modules()
486
+ if not all_modules:
487
+ print_panel("未在数据库中发现注册模块,正在初始化模块列表...", "提示")
488
+ from . import init as init_module
489
+ init_module()
490
+ all_modules = env.get_all_modules()
491
+ if not all_modules:
492
+ print_panel("未找到任何模块", "错误")
493
+ return
494
+ print_panel(f"找到 {len(all_modules)} 个模块", "统计")
495
+ rows = []
496
+ for name, info in all_modules.items():
497
+ status = "✓" if info.get("status", True) else "✗"
498
+ meta = info.get('info', {}).get('meta', {})
499
+ depsinfo = info.get('info', {}).get('dependencies', {})
500
+ optional_deps = depsinfo.get('optional', [])
501
+ available_optional_deps = []
502
+ missing_optional_deps = []
503
+ if optional_deps:
504
+ for dep in optional_deps:
505
+ if isinstance(dep, list):
506
+ available_deps = [d for d in dep if d in all_modules]
507
+ if available_deps:
508
+ available_optional_deps.extend(available_deps)
509
+ else:
510
+ missing_optional_deps.extend(dep)
511
+ elif dep in all_modules:
512
+ available_optional_deps.append(dep)
513
+ else:
514
+ missing_optional_deps.append(dep)
515
+ if missing_optional_deps:
516
+ optional_dependencies = f"可用: {', '.join(available_optional_deps)} 缺失: {', '.join(missing_optional_deps)}"
517
+ else:
518
+ optional_dependencies = ', '.join(available_optional_deps) or '无'
519
+ else:
520
+ optional_dependencies = '无'
521
+ dependencies = ', '.join(depsinfo.get('requires', [])) or '无'
522
+ pip_dependencies = ', '.join(depsinfo.get('pip', [])) or '无'
523
+ rows.append([
524
+ name, status, meta.get('version', '未知'), meta.get('description', '无描述'),
525
+ dependencies, optional_dependencies, pip_dependencies
526
+ ])
527
+ print_table(
528
+ ["模块名称", "状态", "版本", "描述", "依赖", "可选依赖", "pip依赖"],
529
+ rows,
530
+ "模块列表"
531
+ )
532
+ enabled_count = sum(1 for m in all_modules.values() if m.get("status", True))
533
+ disabled_count = len(all_modules) - enabled_count
534
+ print_panel(f"已启用: {enabled_count} 已禁用: {disabled_count}", "模块状态统计")
535
+
536
+ def main():
537
+ parser = argparse.ArgumentParser(
538
+ description="ErisPulse 命令行工具",
539
+ prog="ep"
540
+ )
541
+ subparsers = parser.add_subparsers(dest='command', help='可用命令')
542
+ enable_parser = subparsers.add_parser('enable', help='启用指定模块')
543
+ enable_parser.add_argument('module_names', nargs='+', help='要启用的模块名称(支持多个模块,用空格分隔)')
544
+ enable_parser.add_argument('--init', action='store_true', help='在启用模块前初始化模块数据库')
545
+ disable_parser = subparsers.add_parser('disable', help='禁用指定模块')
546
+ disable_parser.add_argument('module_names', nargs='+', help='要禁用的模块名称(支持多个模块,用空格分隔)')
547
+ disable_parser.add_argument('--init', action='store_true', help='在禁用模块前初始化模块数据库')
548
+ list_parser = subparsers.add_parser('list', help='列出所有模块信息')
549
+ list_parser.add_argument('--module', '-m', type=str, help='指定要展示的模块名称')
550
+ update_parser = subparsers.add_parser('update', help='更新模块列表')
551
+ upgrade_parser = subparsers.add_parser('upgrade', help='升级模块列表')
552
+ upgrade_parser.add_argument('--force', action='store_true', help='跳过二次确认,强制更新')
553
+ uninstall_parser = subparsers.add_parser('uninstall', help='删除指定模块')
554
+ uninstall_parser.add_argument('module_names', nargs='+', help='要卸载的模块名称(支持多个模块,用空格分隔)')
555
+ install_parser = subparsers.add_parser('install', help='安装指定模块(支持多个模块,用空格分隔)')
556
+ install_parser.add_argument('module_name', nargs='+', help='要安装的模块名称(支持多个模块,用空格分隔)')
557
+ install_parser.add_argument('--force', action='store_true', help='强制重新安装模块')
558
+ install_parser.add_argument('--init', action='store_true', help='在安装模块前初始化模块数据库')
559
+ origin_parser = subparsers.add_parser('origin', help='管理模块源')
560
+ origin_subparsers = origin_parser.add_subparsers(dest='origin_command', help='源管理命令')
561
+ add_origin_parser = origin_subparsers.add_parser('add', help='添加模块源')
562
+ add_origin_parser.add_argument('url', type=str, help='要添加的模块源URL')
563
+ list_origin_parser = origin_subparsers.add_parser('list', help='列出所有模块源')
564
+ del_origin_parser = origin_subparsers.add_parser('del', help='删除模块源')
565
+ del_origin_parser.add_argument('url', type=str, help='要删除的模块源URL')
566
+ args = parser.parse_args()
567
+ source_manager = SourceManager()
568
+ if hasattr(args, 'init') and args.init:
569
+ print("正在初始化模块列表...")
570
+ from . import init as init_module
571
+ init_module()
572
+ if args.command == 'enable':
573
+ for module_name in args.module_names:
574
+ module_name = module_name.strip()
575
+ if not module_name:
576
+ continue
577
+ if '*' in module_name or '?' in module_name:
578
+ print(f"正在匹配模块模式: {module_name}...")
579
+ all_modules = env.get_all_modules()
580
+ if not all_modules:
581
+ print_panel("未找到任何模块,请先更新源或检查配置", "错误")
582
+ continue
583
+ matched_modules = [name for name in all_modules.keys() if fnmatch.fnmatch(name, module_name)]
584
+ if not matched_modules:
585
+ print_panel(f"未找到匹配模块模式 {module_name} 的模块", "错误")
586
+ continue
587
+ print(f"找到 {len(matched_modules)} 个匹配模块:")
588
+ for i, matched_module in enumerate(matched_modules, start=1):
589
+ print(f" {i}. {matched_module}")
590
+ if not confirm("是否启用所有匹配模块?", default=True):
591
+ print("操作已取消")
592
+ continue
593
+ for matched_module in matched_modules:
594
+ enable_module(matched_module)
595
+ else:
596
+ enable_module(module_name)
597
+ elif args.command == 'disable':
598
+ for module_name in args.module_names:
599
+ module_name = module_name.strip()
600
+ if not module_name:
601
+ continue
602
+ if '*' in module_name or '?' in module_name:
603
+ print(f"正在匹配模块模式: {module_name}...")
604
+ all_modules = env.get_all_modules()
605
+ if not all_modules:
606
+ print_panel("未找到任何模块,请先更新源或检查配置", "错误")
607
+ continue
608
+ matched_modules = [name for name in all_modules.keys() if fnmatch.fnmatch(name, module_name)]
609
+ if not matched_modules:
610
+ print_panel(f"未找到匹配模块模式 {module_name} 的模块", "错误")
611
+ continue
612
+ print(f"找到 {len(matched_modules)} 个匹配模块:")
613
+ for i, matched_module in enumerate(matched_modules, start=1):
614
+ print(f" {i}. {matched_module}")
615
+ if not confirm("是否禁用所有匹配模块?", default=True):
616
+ print("操作已取消")
617
+ continue
618
+ for matched_module in matched_modules:
619
+ disable_module(matched_module)
620
+ else:
621
+ disable_module(module_name)
622
+ elif args.command == 'list':
623
+ list_modules(args.module)
624
+ elif args.command == 'uninstall':
625
+ for module_name in args.module_names:
626
+ module_name = module_name.strip()
627
+ if not module_name:
628
+ continue
629
+ if '*' in module_name or '?' in module_name:
630
+ print(f"正在匹配模块模式: {module_name}...")
631
+ all_modules = env.get_all_modules()
632
+ if not all_modules:
633
+ print_panel("未找到任何模块,请先更新源或检查配置", "错误")
634
+ continue
635
+ matched_modules = [name for name in all_modules.keys() if fnmatch.fnmatch(name, module_name)]
636
+ if not matched_modules:
637
+ print_panel(f"未找到匹配模块模式 {module_name} 的模块", "错误")
638
+ continue
639
+ print(f"找到 {len(matched_modules)} 个匹配模块:")
640
+ for i, matched_module in enumerate(matched_modules, start=1):
641
+ print(f" {i}. {matched_module}")
642
+ if not confirm("是否卸载所有匹配模块?", default=True):
643
+ print("操作已取消")
644
+ continue
645
+ for matched_module in matched_modules:
646
+ uninstall_module(matched_module)
647
+ else:
648
+ uninstall_module(module_name)
649
+ elif args.command == 'install':
650
+ for module_name in args.module_name:
651
+ module_name = module_name.strip()
652
+ if not module_name:
653
+ continue
654
+ if '*' in module_name or '?' in module_name:
655
+ print(f"正在匹配模块模式: {module_name}...")
656
+ all_modules = env.get_all_modules()
657
+ if not all_modules:
658
+ print_panel("未找到任何模块,请先更新源或检查配置", "错误")
659
+ continue
660
+ matched_modules = [name for name in all_modules.keys() if fnmatch.fnmatch(name, module_name)]
661
+ if not matched_modules:
662
+ print_panel(f"未找到匹配模块模式 {module_name} 的模块", "错误")
663
+ continue
664
+ print(f"找到 {len(matched_modules)} 个匹配模块:")
665
+ for i, matched_module in enumerate(matched_modules, start=1):
666
+ print(f" {i}. {matched_module}")
667
+ if not confirm("是否安装所有匹配模块?", default=True):
668
+ print("安装已取消")
669
+ continue
670
+ for matched_module in matched_modules:
671
+ install_module(matched_module, args.force)
672
+ else:
673
+ install_module(module_name, args.force)
674
+ elif args.command == 'update':
675
+ SourceManager().update_sources()
676
+ elif args.command == 'upgrade':
677
+ upgrade_all_modules(args.force)
678
+ elif args.command == 'origin':
679
+ if args.origin_command == 'add':
680
+ success = source_manager.add_source(args.url)
681
+ if success:
682
+ if confirm("源已添加,是否立即更新源以获取最新模块信息?", default=True):
683
+ source_manager.update_sources()
684
+ elif args.origin_command == 'list':
685
+ source_manager.list_sources()
686
+ elif args.origin_command == 'del':
687
+ source_manager.del_source(args.url)
688
+ else:
689
+ origin_parser.print_help()
690
+ else:
691
+ parser.print_help()
692
+
693
+ if __name__ == "__main__":
694
+ main()