ErisPulse 1.0.7__tar.gz
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-1.0.7/ErisPulse/__init__.py +135 -0
- erispulse-1.0.7/ErisPulse/__main__.py +464 -0
- erispulse-1.0.7/ErisPulse/envManager.py +211 -0
- erispulse-1.0.7/ErisPulse/errors.py +16 -0
- erispulse-1.0.7/ErisPulse/logger.py +60 -0
- erispulse-1.0.7/ErisPulse/origin.py +112 -0
- erispulse-1.0.7/ErisPulse/sdk.py +3 -0
- erispulse-1.0.7/ErisPulse/util.py +30 -0
- erispulse-1.0.7/ErisPulse.egg-info/PKG-INFO +58 -0
- erispulse-1.0.7/ErisPulse.egg-info/SOURCES.txt +15 -0
- erispulse-1.0.7/ErisPulse.egg-info/dependency_links.txt +1 -0
- erispulse-1.0.7/ErisPulse.egg-info/requires.txt +2 -0
- erispulse-1.0.7/ErisPulse.egg-info/top_level.txt +1 -0
- erispulse-1.0.7/PKG-INFO +58 -0
- erispulse-1.0.7/README.md +18 -0
- erispulse-1.0.7/setup.cfg +4 -0
- erispulse-1.0.7/setup.py +33 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from . import sdk
|
|
4
|
+
from . import util
|
|
5
|
+
from . import errors
|
|
6
|
+
from . import logger
|
|
7
|
+
from .envManager import env
|
|
8
|
+
|
|
9
|
+
setattr(sdk, "env", env)
|
|
10
|
+
setattr(sdk, "logger", logger)
|
|
11
|
+
setattr(sdk, "util", util)
|
|
12
|
+
|
|
13
|
+
env.load_env_file()
|
|
14
|
+
|
|
15
|
+
def init():
|
|
16
|
+
try:
|
|
17
|
+
sdkModulePath = os.path.join(os.path.dirname(__file__), "modules")
|
|
18
|
+
|
|
19
|
+
if not os.path.exists(sdkModulePath):
|
|
20
|
+
os.makedirs(sdkModulePath)
|
|
21
|
+
|
|
22
|
+
sys.path.append(sdkModulePath)
|
|
23
|
+
|
|
24
|
+
TempModules = [
|
|
25
|
+
x for x in os.listdir(sdkModulePath)
|
|
26
|
+
if os.path.isdir(os.path.join(sdkModulePath, x))
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
sdkInstalledModuleNames: list[str] = []
|
|
30
|
+
disabledModules: list[str] = []
|
|
31
|
+
|
|
32
|
+
for module_name in TempModules:
|
|
33
|
+
try:
|
|
34
|
+
moduleObj = __import__(module_name)
|
|
35
|
+
if not hasattr(moduleObj, "moduleInfo") or not isinstance(moduleObj.moduleInfo, dict):
|
|
36
|
+
logger.warning(f"模块 {module_name} 缺少有效的 'moduleInfo' 字典.")
|
|
37
|
+
continue
|
|
38
|
+
if "name" not in moduleObj.moduleInfo:
|
|
39
|
+
logger.warning(f"模块 {module_name} 的 'moduleInfo' 字典 缺少必要 'name' 键.")
|
|
40
|
+
continue
|
|
41
|
+
if not hasattr(moduleObj, "Main"):
|
|
42
|
+
logger.warning(f"模块 {module_name} 缺少 'Main' 类.")
|
|
43
|
+
continue
|
|
44
|
+
|
|
45
|
+
module_info = env.get_module(moduleObj.moduleInfo["name"])
|
|
46
|
+
if module_info is None:
|
|
47
|
+
module_info = {
|
|
48
|
+
"status": True,
|
|
49
|
+
"info": moduleObj.moduleInfo
|
|
50
|
+
}
|
|
51
|
+
env.set_module(moduleObj.moduleInfo["name"], module_info)
|
|
52
|
+
logger.info(f"模块 {moduleObj.moduleInfo['name']} 信息已初始化并存储到数据库")
|
|
53
|
+
|
|
54
|
+
if not module_info.get('status', True):
|
|
55
|
+
disabledModules.append(module_name)
|
|
56
|
+
logger.warning(f"模块 {moduleObj.moduleInfo['name']} 已禁用,跳过加载")
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
required_deps = moduleObj.moduleInfo.get("dependencies", [])
|
|
60
|
+
missing_required_deps = [dep for dep in required_deps if dep not in TempModules]
|
|
61
|
+
if missing_required_deps:
|
|
62
|
+
logger.error(f"模块 {module_name} 缺少必需依赖: {missing_required_deps}")
|
|
63
|
+
raise errors.MissingDependencyError(f"模块 {module_name} 缺少必需依赖: {missing_required_deps}")
|
|
64
|
+
|
|
65
|
+
optional_deps = moduleObj.moduleInfo.get("optional_dependencies", [])
|
|
66
|
+
available_optional_deps = []
|
|
67
|
+
|
|
68
|
+
for dep in optional_deps:
|
|
69
|
+
if isinstance(dep, list):
|
|
70
|
+
if all(d in TempModules for d in dep):
|
|
71
|
+
available_optional_deps.extend(dep)
|
|
72
|
+
elif dep in TempModules:
|
|
73
|
+
available_optional_deps.append(dep)
|
|
74
|
+
|
|
75
|
+
if optional_deps and not available_optional_deps:
|
|
76
|
+
logger.warning(f"模块 {module_name} 缺少所有可选依赖: {optional_deps}")
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
sdkInstalledModuleNames.append(module_name)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.warning(f"模块 {module_name} 加载失败: {e}")
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
sdkModuleDependencies = {}
|
|
85
|
+
for module_name in sdkInstalledModuleNames:
|
|
86
|
+
moduleObj = __import__(module_name)
|
|
87
|
+
moduleDependecies: list[str] = moduleObj.moduleInfo.get("dependencies", [])
|
|
88
|
+
|
|
89
|
+
optional_deps = moduleObj.moduleInfo.get("optional_dependencies", [])
|
|
90
|
+
available_optional_deps = [dep for dep in optional_deps if dep in sdkInstalledModuleNames]
|
|
91
|
+
moduleDependecies.extend(available_optional_deps)
|
|
92
|
+
|
|
93
|
+
for dep in moduleDependecies:
|
|
94
|
+
if dep in disabledModules:
|
|
95
|
+
logger.warning(f"模块 {module_name} 的依赖模块 {dep} 已禁用,跳过加载")
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
if not all(dep in sdkInstalledModuleNames for dep in moduleDependecies):
|
|
99
|
+
raise errors.InvalidDependencyError(
|
|
100
|
+
f"模块 {module_name} 的依赖无效: {moduleDependecies}"
|
|
101
|
+
)
|
|
102
|
+
sdkModuleDependencies[module_name] = moduleDependecies
|
|
103
|
+
|
|
104
|
+
sdkInstalledModuleNames: list[str] = sdk.util.topological_sort(
|
|
105
|
+
sdkInstalledModuleNames, sdkModuleDependencies, errors.CycleDependencyError
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
all_modules_info = {}
|
|
109
|
+
for module_name in sdkInstalledModuleNames:
|
|
110
|
+
moduleObj = __import__(module_name)
|
|
111
|
+
moduleInfo: dict = moduleObj.moduleInfo
|
|
112
|
+
|
|
113
|
+
module_info = env.get_module(moduleInfo["name"])
|
|
114
|
+
env.set_module(moduleInfo["name"], {
|
|
115
|
+
"status": True,
|
|
116
|
+
"info": moduleInfo
|
|
117
|
+
})
|
|
118
|
+
logger.debug("所有模块信息已加载并存储到数据库")
|
|
119
|
+
|
|
120
|
+
for module_name in sdkInstalledModuleNames:
|
|
121
|
+
moduleObj = __import__(module_name)
|
|
122
|
+
moduleInfo = moduleObj.moduleInfo
|
|
123
|
+
module_status = env.get_module_status(moduleInfo["name"])
|
|
124
|
+
if not module_status:
|
|
125
|
+
continue
|
|
126
|
+
|
|
127
|
+
moduleMain = moduleObj.Main(sdk, logger)
|
|
128
|
+
setattr(moduleMain, "moduleInfo", moduleInfo)
|
|
129
|
+
setattr(sdk, moduleInfo["name"], moduleMain)
|
|
130
|
+
logger.debug(f"模块 {moduleInfo['name']} 正在初始化")
|
|
131
|
+
except Exception as e:
|
|
132
|
+
logger.error(f"初始化失败: {e}")
|
|
133
|
+
raise e
|
|
134
|
+
|
|
135
|
+
sdk.init = init
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import shutil
|
|
5
|
+
import aiohttp
|
|
6
|
+
import zipfile
|
|
7
|
+
import asyncio
|
|
8
|
+
import subprocess
|
|
9
|
+
import json
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
from rich.prompt import Prompt, Confirm
|
|
14
|
+
from .envManager import env
|
|
15
|
+
from .origin import origin_manager
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
def enable_module(module_name):
|
|
20
|
+
module_info = env.get_module(module_name)
|
|
21
|
+
if module_info:
|
|
22
|
+
env.set_module_status(module_name, True)
|
|
23
|
+
console.print(f"[green]模块 {module_name} 已启用[/green]")
|
|
24
|
+
else:
|
|
25
|
+
console.print(f"[red]模块 {module_name} 不存在[/red]")
|
|
26
|
+
|
|
27
|
+
def disable_module(module_name):
|
|
28
|
+
module_info = env.get_module(module_name)
|
|
29
|
+
if module_info:
|
|
30
|
+
env.set_module_status(module_name, False)
|
|
31
|
+
console.print(f"[yellow]模块 {module_name} 已禁用[/yellow]")
|
|
32
|
+
else:
|
|
33
|
+
console.print(f"[red]模块 {module_name} 不存在[/red]")
|
|
34
|
+
|
|
35
|
+
async def fetch_url(session, url):
|
|
36
|
+
try:
|
|
37
|
+
async with session.get(url) as response:
|
|
38
|
+
response.raise_for_status()
|
|
39
|
+
return await response.read()
|
|
40
|
+
except Exception as e:
|
|
41
|
+
console.print(f"[red]请求失败: {e}[/red]")
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
def extract_and_setup_module(module_name, module_url, zip_path, module_dir):
|
|
45
|
+
try:
|
|
46
|
+
console.print(f"[cyan]正在从 {module_url} 下载模块...[/cyan]")
|
|
47
|
+
|
|
48
|
+
async def download_module():
|
|
49
|
+
async with aiohttp.ClientSession() as session:
|
|
50
|
+
content = await fetch_url(session, module_url)
|
|
51
|
+
if content is None:
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
with open(zip_path, 'wb') as zip_file:
|
|
55
|
+
zip_file.write(content)
|
|
56
|
+
|
|
57
|
+
if not os.path.exists(module_dir):
|
|
58
|
+
os.makedirs(module_dir)
|
|
59
|
+
|
|
60
|
+
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
|
61
|
+
zip_ref.extractall(module_dir)
|
|
62
|
+
|
|
63
|
+
init_file_path = os.path.join(module_dir, '__init__.py')
|
|
64
|
+
if not os.path.exists(init_file_path):
|
|
65
|
+
sub_module_dir = os.path.join(module_dir, module_name)
|
|
66
|
+
m_sub_module_dir = os.path.join(module_dir, f"m_{module_name}")
|
|
67
|
+
for sub_dir in [sub_module_dir, m_sub_module_dir]:
|
|
68
|
+
if os.path.exists(sub_dir) and os.path.isdir(sub_dir):
|
|
69
|
+
for item in os.listdir(sub_dir):
|
|
70
|
+
source_item = os.path.join(sub_dir, item)
|
|
71
|
+
target_item = os.path.join(module_dir, item)
|
|
72
|
+
if os.path.exists(target_item):
|
|
73
|
+
os.remove(target_item)
|
|
74
|
+
shutil.move(source_item, module_dir)
|
|
75
|
+
os.rmdir(sub_dir)
|
|
76
|
+
|
|
77
|
+
console.print(f"[green]模块 {module_name} 文件已成功解压并设置[/green]")
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
return asyncio.run(download_module())
|
|
81
|
+
|
|
82
|
+
except Exception as e:
|
|
83
|
+
console.print(Panel(f"[red]处理模块 {module_name} 文件失败: {e}[/red]", title="错误", border_style="red"))
|
|
84
|
+
if os.path.exists(zip_path):
|
|
85
|
+
try:
|
|
86
|
+
os.remove(zip_path)
|
|
87
|
+
except Exception as cleanup_error:
|
|
88
|
+
console.print(f"[red]清理失败: {cleanup_error}[/red]")
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
finally:
|
|
92
|
+
if os.path.exists(zip_path):
|
|
93
|
+
try:
|
|
94
|
+
os.remove(zip_path)
|
|
95
|
+
except Exception as cleanup_error:
|
|
96
|
+
console.print(f"[red]清理失败: {cleanup_error}[/red]")
|
|
97
|
+
|
|
98
|
+
def install_pip_dependencies(dependencies):
|
|
99
|
+
if not dependencies:
|
|
100
|
+
return True
|
|
101
|
+
|
|
102
|
+
console.print("[cyan]正在安装pip依赖...[/cyan]")
|
|
103
|
+
try:
|
|
104
|
+
result = subprocess.run(
|
|
105
|
+
[sys.executable, "-m", "pip", "install"] + dependencies,
|
|
106
|
+
check=True,
|
|
107
|
+
stdout=subprocess.PIPE,
|
|
108
|
+
stderr=subprocess.PIPE
|
|
109
|
+
)
|
|
110
|
+
console.print(result.stdout.decode())
|
|
111
|
+
return True
|
|
112
|
+
except subprocess.CalledProcessError as e:
|
|
113
|
+
console.print(Panel(f"[red]安装pip依赖失败: {e.stderr.decode()}[/red]", title="错误", border_style="red"))
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
def install_module(module_name, force=False):
|
|
117
|
+
module_info = env.get_module(module_name)
|
|
118
|
+
if module_info and not force:
|
|
119
|
+
console.print(f"[yellow]模块 {module_name} 已存在,使用 --force 参数强制重装[/yellow]")
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
providers = env.get('providers', {})
|
|
123
|
+
if isinstance(providers, str):
|
|
124
|
+
providers = json.loads(providers)
|
|
125
|
+
|
|
126
|
+
module_info = []
|
|
127
|
+
for provider, url in providers.items():
|
|
128
|
+
module_key = f"{module_name}@{provider}"
|
|
129
|
+
modules_data = env.get('modules', {})
|
|
130
|
+
if isinstance(modules_data, str):
|
|
131
|
+
modules_data = json.loads(modules_data)
|
|
132
|
+
|
|
133
|
+
if module_key in modules_data:
|
|
134
|
+
module_data = modules_data[module_key]
|
|
135
|
+
module_info.append({
|
|
136
|
+
'provider': provider,
|
|
137
|
+
'url': url,
|
|
138
|
+
'path': module_data.get('path', ''),
|
|
139
|
+
'version': module_data.get('version', '未知'),
|
|
140
|
+
'description': module_data.get('description', '无描述'),
|
|
141
|
+
'author': module_data.get('author', '未知'),
|
|
142
|
+
'dependencies': module_data.get('dependencies', []),
|
|
143
|
+
'optional_dependencies': module_data.get('optional_dependencies', []),
|
|
144
|
+
'pip_dependencies': module_data.get('pip_dependencies', [])
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
if not module_info:
|
|
148
|
+
console.print(f"[red]未找到模块 {module_name}[/red]")
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
if len(module_info) > 1:
|
|
152
|
+
console.print(f"[cyan]找到 {len(module_info)} 个源的 {module_name} 模块:[/cyan]")
|
|
153
|
+
table = Table(title="可选模块源", show_header=True, header_style="bold magenta")
|
|
154
|
+
table.add_column("编号", style="cyan")
|
|
155
|
+
table.add_column("源", style="green")
|
|
156
|
+
table.add_column("版本", style="blue")
|
|
157
|
+
table.add_column("描述", style="white")
|
|
158
|
+
table.add_column("作者", style="yellow")
|
|
159
|
+
for i, info in enumerate(module_info):
|
|
160
|
+
table.add_row(str(i+1), info['provider'], info['version'], info['description'], info['author'])
|
|
161
|
+
console.print(table)
|
|
162
|
+
|
|
163
|
+
while True:
|
|
164
|
+
choice = Prompt.ask("请选择要安装的源 (输入编号)", default="1")
|
|
165
|
+
if choice.isdigit() and 1 <= int(choice) <= len(module_info):
|
|
166
|
+
selected_module = module_info[int(choice)-1]
|
|
167
|
+
break
|
|
168
|
+
else:
|
|
169
|
+
console.print("[red]输入无效,请重新选择[/red]")
|
|
170
|
+
else:
|
|
171
|
+
selected_module = module_info[0]
|
|
172
|
+
|
|
173
|
+
for dep in selected_module['dependencies']:
|
|
174
|
+
console.print(f"[cyan]正在安装依赖模块 {dep}...[/cyan]")
|
|
175
|
+
install_module(dep)
|
|
176
|
+
|
|
177
|
+
third_party_deps = selected_module.get('pip_dependencies', [])
|
|
178
|
+
if third_party_deps:
|
|
179
|
+
console.print(f"[cyan]模块 {module_name} 需要以下pip依赖: {', '.join(third_party_deps)}[/cyan]")
|
|
180
|
+
if not install_pip_dependencies(third_party_deps):
|
|
181
|
+
console.print(f"[red]无法安装模块 {module_name} 的pip依赖,安装终止[/red]")
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
module_url = selected_module['url'] + selected_module['path']
|
|
185
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
186
|
+
module_dir = os.path.join(script_dir, 'modules', module_name)
|
|
187
|
+
zip_path = os.path.join(script_dir, f"{module_name}.zip")
|
|
188
|
+
|
|
189
|
+
if not extract_and_setup_module(
|
|
190
|
+
module_name=module_name,
|
|
191
|
+
module_url=module_url,
|
|
192
|
+
zip_path=zip_path,
|
|
193
|
+
module_dir=module_dir
|
|
194
|
+
):
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
env.set_module(module_name, {
|
|
198
|
+
'status': True,
|
|
199
|
+
'info': {
|
|
200
|
+
'version': selected_module['version'],
|
|
201
|
+
'description': selected_module['description'],
|
|
202
|
+
'author': selected_module['author'],
|
|
203
|
+
'dependencies': selected_module['dependencies'],
|
|
204
|
+
'optional_dependencies': selected_module['optional_dependencies'],
|
|
205
|
+
'pip_dependencies': selected_module['pip_dependencies']
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
console.print(f"[green]模块 {module_name} 安装成功[/green]")
|
|
209
|
+
|
|
210
|
+
def uninstall_module(module_name):
|
|
211
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
212
|
+
module_path = os.path.join(script_dir, 'modules', module_name)
|
|
213
|
+
|
|
214
|
+
module_file_path = module_path + '.py'
|
|
215
|
+
if os.path.exists(module_file_path):
|
|
216
|
+
try:
|
|
217
|
+
os.remove(module_file_path)
|
|
218
|
+
except Exception as e:
|
|
219
|
+
console.print(f"[red]删除模块文件 {module_name} 时出错: {e}[/red]")
|
|
220
|
+
elif os.path.exists(module_path) and os.path.isdir(module_path):
|
|
221
|
+
try:
|
|
222
|
+
shutil.rmtree(module_path)
|
|
223
|
+
except Exception as e:
|
|
224
|
+
console.print(f"[red]删除模块目录 {module_name} 时出错: {e}[/red]")
|
|
225
|
+
else:
|
|
226
|
+
console.print(f"[red]模块 {module_name} 不存在[/red]")
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
module_info = env.get_module(module_name)
|
|
230
|
+
if not module_info:
|
|
231
|
+
console.print(f"[red]模块 {module_name} 不存在[/red]")
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
pip_dependencies = module_info.get('info', {}).get('pip_dependencies', [])
|
|
235
|
+
if pip_dependencies:
|
|
236
|
+
all_modules = env.get_all_modules()
|
|
237
|
+
unused_pip_dependencies = []
|
|
238
|
+
|
|
239
|
+
essential_packages = {'aiohttp', 'rich'}
|
|
240
|
+
|
|
241
|
+
for dep in pip_dependencies:
|
|
242
|
+
if dep in essential_packages:
|
|
243
|
+
console.print(f"[yellow]跳过必要模块 {dep} 的卸载[/yellow]")
|
|
244
|
+
continue
|
|
245
|
+
|
|
246
|
+
is_dependency_used = False
|
|
247
|
+
for name, info in all_modules.items():
|
|
248
|
+
if name != module_name and dep in info.get('info', {}).get('pip_dependencies', []):
|
|
249
|
+
is_dependency_used = True
|
|
250
|
+
break
|
|
251
|
+
if not is_dependency_used:
|
|
252
|
+
unused_pip_dependencies.append(dep)
|
|
253
|
+
|
|
254
|
+
if unused_pip_dependencies:
|
|
255
|
+
console.print(f"[cyan]以下 pip 依赖不再被其他模块使用: {', '.join(unused_pip_dependencies)}[/cyan]")
|
|
256
|
+
confirm = Confirm.ask("[yellow]是否卸载这些 pip 依赖?[/yellow]", default=False)
|
|
257
|
+
if confirm:
|
|
258
|
+
console.print(f"[cyan]正在卸载 pip 依赖: {', '.join(unused_pip_dependencies)}[/cyan]")
|
|
259
|
+
try:
|
|
260
|
+
subprocess.run(
|
|
261
|
+
[sys.executable, "-m", "pip", "uninstall", "-y"] + unused_pip_dependencies,
|
|
262
|
+
check=True,
|
|
263
|
+
stdout=subprocess.PIPE,
|
|
264
|
+
stderr=subprocess.PIPE
|
|
265
|
+
)
|
|
266
|
+
console.print(f"[green]成功卸载 pip 依赖: {', '.join(unused_pip_dependencies)}[/green]")
|
|
267
|
+
except subprocess.CalledProcessError as e:
|
|
268
|
+
console.print(Panel(f"[red]卸载 pip 依赖失败: {e.stderr.decode()}[/red]", title="错误", border_style="red"))
|
|
269
|
+
|
|
270
|
+
if env.remove_module(module_name):
|
|
271
|
+
console.print(f"[green]模块 {module_name} 已删除[/green]")
|
|
272
|
+
else:
|
|
273
|
+
console.print(f"[red]模块 {module_name} 不存在[/red]")
|
|
274
|
+
def upgrade_all_modules(force=False):
|
|
275
|
+
all_modules = env.get_all_modules()
|
|
276
|
+
if not all_modules:
|
|
277
|
+
console.print("[yellow]未找到任何模块,无法更新[/yellow]")
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
providers = env.get('providers', {})
|
|
281
|
+
if isinstance(providers, str):
|
|
282
|
+
providers = json.loads(providers)
|
|
283
|
+
|
|
284
|
+
modules_data = env.get('modules', {})
|
|
285
|
+
if isinstance(modules_data, str):
|
|
286
|
+
modules_data = json.loads(modules_data)
|
|
287
|
+
|
|
288
|
+
updates_available = []
|
|
289
|
+
for module_name, module_info in all_modules.items():
|
|
290
|
+
local_version = module_info['info'].get('version', '0.0.0')
|
|
291
|
+
for provider, url in providers.items():
|
|
292
|
+
module_key = f"{module_name}@{provider}"
|
|
293
|
+
if module_key in modules_data:
|
|
294
|
+
remote_module = modules_data[module_key]
|
|
295
|
+
remote_version = remote_module.get('version', '0.0.0')
|
|
296
|
+
if remote_version > local_version:
|
|
297
|
+
updates_available.append({
|
|
298
|
+
'name': module_name,
|
|
299
|
+
'local_version': local_version,
|
|
300
|
+
'remote_version': remote_version,
|
|
301
|
+
'provider': provider,
|
|
302
|
+
'url': url,
|
|
303
|
+
'path': remote_module.get('path', ''),
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
if not updates_available:
|
|
307
|
+
console.print("[green]所有模块已是最新版本,无需更新[/green]")
|
|
308
|
+
return
|
|
309
|
+
|
|
310
|
+
console.print("\n[cyan]以下模块有可用更新:[/cyan]")
|
|
311
|
+
table = Table(title="可用更新", show_header=True, header_style="bold magenta")
|
|
312
|
+
table.add_column("模块", style="cyan")
|
|
313
|
+
table.add_column("当前版本", style="yellow")
|
|
314
|
+
table.add_column("最新版本", style="green")
|
|
315
|
+
table.add_column("源", style="blue")
|
|
316
|
+
for update in updates_available:
|
|
317
|
+
table.add_row(update['name'], update['local_version'], update['remote_version'], update['provider'])
|
|
318
|
+
console.print(table)
|
|
319
|
+
|
|
320
|
+
if not force:
|
|
321
|
+
confirm = Confirm.ask("[yellow]警告:更新模块可能会导致兼容性问题,请在更新前查看插件作者的相关声明。\n是否继续?[/yellow]", default=False)
|
|
322
|
+
if not confirm:
|
|
323
|
+
console.print("[yellow]更新已取消[/yellow]")
|
|
324
|
+
return
|
|
325
|
+
|
|
326
|
+
for update in updates_available:
|
|
327
|
+
console.print(f"[cyan]正在更新模块 {update['name']}...[/cyan]")
|
|
328
|
+
module_url = update['url'] + update['path']
|
|
329
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
330
|
+
module_dir = os.path.join(script_dir, 'modules', update['name'])
|
|
331
|
+
zip_path = os.path.join(script_dir, f"{update['name']}.zip")
|
|
332
|
+
|
|
333
|
+
if not extract_and_setup_module(
|
|
334
|
+
module_name=update['name'],
|
|
335
|
+
module_url=module_url,
|
|
336
|
+
zip_path=zip_path,
|
|
337
|
+
module_dir=module_dir
|
|
338
|
+
):
|
|
339
|
+
continue
|
|
340
|
+
|
|
341
|
+
all_modules[update['name']]['info']['version'] = update['remote_version']
|
|
342
|
+
env.set_all_modules(all_modules)
|
|
343
|
+
console.print(f"[green]模块 {update['name']} 已更新至版本 {update['remote_version']}[/green]")
|
|
344
|
+
|
|
345
|
+
def list_modules(module_name=None):
|
|
346
|
+
all_modules = env.get_all_modules()
|
|
347
|
+
if not all_modules:
|
|
348
|
+
console.print("[yellow]未在数据库中发现注册模块,正在初始化模块列表...[/yellow]")
|
|
349
|
+
from . import init as init_module
|
|
350
|
+
init_module()
|
|
351
|
+
all_modules = env.get_all_modules()
|
|
352
|
+
|
|
353
|
+
if not all_modules:
|
|
354
|
+
console.print("[red]未找到任何模块[/red]")
|
|
355
|
+
return
|
|
356
|
+
|
|
357
|
+
table = Table(title="模块列表", show_header=True, header_style="bold magenta")
|
|
358
|
+
table.add_column("模块名称", style="cyan")
|
|
359
|
+
table.add_column("状态", style="green")
|
|
360
|
+
table.add_column("版本", style="blue")
|
|
361
|
+
table.add_column("描述", style="white")
|
|
362
|
+
table.add_column("依赖", style="yellow")
|
|
363
|
+
table.add_column("可选依赖", style="magenta")
|
|
364
|
+
table.add_column("pip依赖", style="cyan")
|
|
365
|
+
|
|
366
|
+
for name, info in all_modules.items():
|
|
367
|
+
status = "启用" if info.get("status", True) else "禁用"
|
|
368
|
+
dependencies = ', '.join(info['info'].get('dependencies', [])) if info['info'].get('dependencies') else '无'
|
|
369
|
+
optional_dependencies = ', '.join(info['info'].get('optional_dependencies', [])) if info['info'].get('optional_dependencies') else '无'
|
|
370
|
+
pip_dependencies = ', '.join(info['info'].get('pip_dependencies', [])) if info['info'].get('pip_dependencies') else '无'
|
|
371
|
+
table.add_row(
|
|
372
|
+
name,
|
|
373
|
+
status,
|
|
374
|
+
info['info'].get('version', '未知'),
|
|
375
|
+
info['info'].get('description', '无描述'),
|
|
376
|
+
dependencies,
|
|
377
|
+
optional_dependencies,
|
|
378
|
+
pip_dependencies
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
console.print(table)
|
|
382
|
+
|
|
383
|
+
def main():
|
|
384
|
+
parser = argparse.ArgumentParser(
|
|
385
|
+
description="ErisPulse 命令行工具",
|
|
386
|
+
prog="python -m ErisPulse"
|
|
387
|
+
)
|
|
388
|
+
subparsers = parser.add_subparsers(dest='command', help='可用命令')
|
|
389
|
+
|
|
390
|
+
enable_parser = subparsers.add_parser('enable', help='启用指定模块')
|
|
391
|
+
enable_parser.add_argument('module_name', type=str, help='要启用的模块名称')
|
|
392
|
+
enable_parser.add_argument('--init', action='store_true', help='在启用模块前初始化模块数据库')
|
|
393
|
+
|
|
394
|
+
disable_parser = subparsers.add_parser('disable', help='禁用指定模块')
|
|
395
|
+
disable_parser.add_argument('module_name', type=str, help='要禁用的模块名称')
|
|
396
|
+
disable_parser.add_argument('--init', action='store_true', help='在禁用模块前初始化模块数据库')
|
|
397
|
+
|
|
398
|
+
list_parser = subparsers.add_parser('list', help='列出所有模块信息')
|
|
399
|
+
list_parser.add_argument('--module', '-m', type=str, help='指定要展示的模块名称')
|
|
400
|
+
|
|
401
|
+
update_parser = subparsers.add_parser('update', help='更新模块列表')
|
|
402
|
+
|
|
403
|
+
upgrade_parser = subparsers.add_parser('upgrade', help='升级模块列表')
|
|
404
|
+
upgrade_parser.add_argument('--force', action='store_true', help='跳过二次确认,强制更新')
|
|
405
|
+
|
|
406
|
+
uninstall_parser = subparsers.add_parser('uninstall', help='删除指定模块')
|
|
407
|
+
uninstall_parser.add_argument('module_name', type=str, help='要删除的模块名称')
|
|
408
|
+
|
|
409
|
+
install_parser = subparsers.add_parser('install', help='安装指定模块(支持多个模块,用逗号分隔)')
|
|
410
|
+
install_parser.add_argument('module_name', type=str, help='要安装的模块名称')
|
|
411
|
+
install_parser.add_argument('--force', action='store_true', help='强制重新安装模块')
|
|
412
|
+
install_parser.add_argument('--init', action='store_true', help='在安装模块前初始化模块数据库')
|
|
413
|
+
|
|
414
|
+
origin_parser = subparsers.add_parser('origin', help='管理模块源')
|
|
415
|
+
origin_subparsers = origin_parser.add_subparsers(dest='origin_command', help='源管理命令')
|
|
416
|
+
|
|
417
|
+
add_origin_parser = origin_subparsers.add_parser('add', help='添加模块源')
|
|
418
|
+
add_origin_parser.add_argument('url', type=str, help='要添加的模块源URL')
|
|
419
|
+
|
|
420
|
+
list_origin_parser = origin_subparsers.add_parser('list', help='列出所有模块源')
|
|
421
|
+
|
|
422
|
+
del_origin_parser = origin_subparsers.add_parser('del', help='删除模块源')
|
|
423
|
+
del_origin_parser.add_argument('url', type=str, help='要删除的模块源URL')
|
|
424
|
+
|
|
425
|
+
args = parser.parse_args()
|
|
426
|
+
|
|
427
|
+
if hasattr(args, 'init') and args.init:
|
|
428
|
+
console.print("[yellow]正在初始化模块列表...[/yellow]")
|
|
429
|
+
from . import init as init_module
|
|
430
|
+
init_module()
|
|
431
|
+
|
|
432
|
+
# 全部指令:enable, disable, list, uninstall, install, update, origin(add, list, del)
|
|
433
|
+
if args.command == 'enable':
|
|
434
|
+
enable_module(args.module_name)
|
|
435
|
+
elif args.command == 'disable':
|
|
436
|
+
disable_module(args.module_name)
|
|
437
|
+
elif args.command == 'list':
|
|
438
|
+
list_modules(args.module)
|
|
439
|
+
elif args.command == 'uninstall':
|
|
440
|
+
uninstall_module(args.module_name)
|
|
441
|
+
elif args.command == 'install':
|
|
442
|
+
module_names = args.module_name.split(',')
|
|
443
|
+
for module_name in module_names:
|
|
444
|
+
module_name = module_name.strip()
|
|
445
|
+
if module_name:
|
|
446
|
+
install_module(module_name, args.force)
|
|
447
|
+
elif args.command == 'update':
|
|
448
|
+
origin_manager.update_origins()
|
|
449
|
+
elif args.command == 'upgrade':
|
|
450
|
+
upgrade_all_modules(args.force)
|
|
451
|
+
elif args.command == 'origin':
|
|
452
|
+
if args.origin_command == 'add':
|
|
453
|
+
origin_manager.add_origin(args.url)
|
|
454
|
+
elif args.origin_command == 'list':
|
|
455
|
+
origin_manager.list_origins()
|
|
456
|
+
elif args.origin_command == 'del':
|
|
457
|
+
origin_manager.del_origin(args.url)
|
|
458
|
+
else:
|
|
459
|
+
origin_parser.print_help()
|
|
460
|
+
else:
|
|
461
|
+
parser.print_help()
|
|
462
|
+
|
|
463
|
+
if __name__ == "__main__":
|
|
464
|
+
main()
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import sqlite3
|
|
4
|
+
import importlib.util
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
class EnvManager:
|
|
8
|
+
_instance = None
|
|
9
|
+
db_path = os.path.join(os.path.dirname(__file__), "config.db")
|
|
10
|
+
|
|
11
|
+
def __new__(cls, *args, **kwargs):
|
|
12
|
+
if not cls._instance:
|
|
13
|
+
cls._instance = super().__new__(cls)
|
|
14
|
+
return cls._instance
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
if not hasattr(self, "_initialized"):
|
|
18
|
+
self._init_db()
|
|
19
|
+
|
|
20
|
+
def _init_db(self):
|
|
21
|
+
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
|
|
22
|
+
|
|
23
|
+
conn = sqlite3.connect(self.db_path)
|
|
24
|
+
cursor = conn.cursor()
|
|
25
|
+
cursor.execute("""
|
|
26
|
+
CREATE TABLE IF NOT EXISTS config (
|
|
27
|
+
key TEXT PRIMARY KEY,
|
|
28
|
+
value TEXT NOT NULL
|
|
29
|
+
)
|
|
30
|
+
""")
|
|
31
|
+
cursor.execute("""
|
|
32
|
+
CREATE TABLE IF NOT EXISTS modules (
|
|
33
|
+
module_name TEXT PRIMARY KEY,
|
|
34
|
+
status INTEGER NOT NULL,
|
|
35
|
+
version TEXT,
|
|
36
|
+
description TEXT,
|
|
37
|
+
author TEXT,
|
|
38
|
+
dependencies TEXT,
|
|
39
|
+
optional_dependencies TEXT,
|
|
40
|
+
pip_dependencies TEXT
|
|
41
|
+
)
|
|
42
|
+
""")
|
|
43
|
+
conn.commit()
|
|
44
|
+
conn.close()
|
|
45
|
+
|
|
46
|
+
def get(self, key, default=None):
|
|
47
|
+
try:
|
|
48
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
49
|
+
cursor = conn.cursor()
|
|
50
|
+
cursor.execute("SELECT value FROM config WHERE key = ?", (key,))
|
|
51
|
+
result = cursor.fetchone()
|
|
52
|
+
if result:
|
|
53
|
+
try:
|
|
54
|
+
return json.loads(result[0])
|
|
55
|
+
except json.JSONDecodeError:
|
|
56
|
+
return result[0]
|
|
57
|
+
return default
|
|
58
|
+
except sqlite3.OperationalError as e:
|
|
59
|
+
if "no such table" in str(e):
|
|
60
|
+
self._init_db()
|
|
61
|
+
return self.get(key, default)
|
|
62
|
+
else:
|
|
63
|
+
raise
|
|
64
|
+
|
|
65
|
+
def set(self, key, value):
|
|
66
|
+
serialized_value = json.dumps(value) if isinstance(value, (dict, list)) else str(value)
|
|
67
|
+
conn = sqlite3.connect(self.db_path)
|
|
68
|
+
cursor = conn.cursor()
|
|
69
|
+
cursor.execute("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)", (key, serialized_value))
|
|
70
|
+
conn.commit()
|
|
71
|
+
conn.close()
|
|
72
|
+
|
|
73
|
+
def delete(self, key):
|
|
74
|
+
conn = sqlite3.connect(self.db_path)
|
|
75
|
+
cursor = conn.cursor()
|
|
76
|
+
cursor.execute("DELETE FROM config WHERE key = ?", (key,))
|
|
77
|
+
conn.commit()
|
|
78
|
+
conn.close()
|
|
79
|
+
|
|
80
|
+
def clear(self):
|
|
81
|
+
conn = sqlite3.connect(self.db_path)
|
|
82
|
+
cursor = conn.cursor()
|
|
83
|
+
cursor.execute("DELETE FROM config")
|
|
84
|
+
conn.commit()
|
|
85
|
+
conn.close()
|
|
86
|
+
|
|
87
|
+
def load_env_file(self):
|
|
88
|
+
env_file = Path("env.py")
|
|
89
|
+
if env_file.exists():
|
|
90
|
+
spec = importlib.util.spec_from_file_location("env_module", env_file)
|
|
91
|
+
env_module = importlib.util.module_from_spec(spec)
|
|
92
|
+
spec.loader.exec_module(env_module)
|
|
93
|
+
for key, value in vars(env_module).items():
|
|
94
|
+
if not key.startswith("__") and isinstance(value, (dict, list, str, int, float, bool)):
|
|
95
|
+
self.set(key, value)
|
|
96
|
+
def set_module_status(self, module_name, status):
|
|
97
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
98
|
+
cursor = conn.cursor()
|
|
99
|
+
cursor.execute("""
|
|
100
|
+
UPDATE modules SET status = ? WHERE module_name = ?
|
|
101
|
+
""", (int(status), module_name))
|
|
102
|
+
conn.commit()
|
|
103
|
+
|
|
104
|
+
def get_module_status(self, module_name):
|
|
105
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
106
|
+
cursor = conn.cursor()
|
|
107
|
+
cursor.execute("""
|
|
108
|
+
SELECT status FROM modules WHERE module_name = ?
|
|
109
|
+
""", (module_name,))
|
|
110
|
+
result = cursor.fetchone()
|
|
111
|
+
return bool(result[0]) if result else True
|
|
112
|
+
|
|
113
|
+
def set_all_modules(self, modules_info):
|
|
114
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
115
|
+
cursor = conn.cursor()
|
|
116
|
+
for module_name, module_info in modules_info.items():
|
|
117
|
+
cursor.execute("""
|
|
118
|
+
INSERT OR REPLACE INTO modules (
|
|
119
|
+
module_name, status, version, description, author,
|
|
120
|
+
dependencies, optional_dependencies, pip_dependencies
|
|
121
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
122
|
+
""", (
|
|
123
|
+
module_name,
|
|
124
|
+
int(module_info.get('status', True)),
|
|
125
|
+
module_info.get('info', {}).get('version', ''),
|
|
126
|
+
module_info.get('info', {}).get('description', ''),
|
|
127
|
+
module_info.get('info', {}).get('author', ''),
|
|
128
|
+
json.dumps(module_info.get('info', {}).get('dependencies', [])),
|
|
129
|
+
json.dumps(module_info.get('info', {}).get('optional_dependencies', [])),
|
|
130
|
+
json.dumps(module_info.get('info', {}).get('pip_dependencies', [])) # 新增
|
|
131
|
+
))
|
|
132
|
+
conn.commit()
|
|
133
|
+
|
|
134
|
+
def get_all_modules(self):
|
|
135
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
136
|
+
cursor = conn.cursor()
|
|
137
|
+
cursor.execute("SELECT * FROM modules")
|
|
138
|
+
rows = cursor.fetchall()
|
|
139
|
+
modules_info = {}
|
|
140
|
+
for row in rows:
|
|
141
|
+
module_name, status, version, description, author, dependencies, optional_dependencies, pip_dependencies = row
|
|
142
|
+
modules_info[module_name] = {
|
|
143
|
+
'status': bool(status),
|
|
144
|
+
'info': {
|
|
145
|
+
'version': version,
|
|
146
|
+
'description': description,
|
|
147
|
+
'author': author,
|
|
148
|
+
'dependencies': json.loads(dependencies) if dependencies else [],
|
|
149
|
+
'optional_dependencies': json.loads(optional_dependencies) if optional_dependencies else [],
|
|
150
|
+
'pip_dependencies': json.loads(pip_dependencies) if pip_dependencies else [] # 新增
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return modules_info
|
|
154
|
+
|
|
155
|
+
def get_module(self, module_name):
|
|
156
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
157
|
+
cursor = conn.cursor()
|
|
158
|
+
cursor.execute("SELECT * FROM modules WHERE module_name = ?", (module_name,))
|
|
159
|
+
row = cursor.fetchone()
|
|
160
|
+
if row:
|
|
161
|
+
module_name, status, version, description, author, dependencies, optional_dependencies, pip_dependencies = row
|
|
162
|
+
return {
|
|
163
|
+
'status': bool(status),
|
|
164
|
+
'info': {
|
|
165
|
+
'version': version,
|
|
166
|
+
'description': description,
|
|
167
|
+
'author': author,
|
|
168
|
+
'dependencies': json.loads(dependencies) if dependencies else [],
|
|
169
|
+
'optional_dependencies': json.loads(optional_dependencies) if optional_dependencies else [],
|
|
170
|
+
'pip_dependencies': json.loads(pip_dependencies) if pip_dependencies else [] # 新增
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
def set_module(self, module_name, module_info):
|
|
176
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
177
|
+
cursor = conn.cursor()
|
|
178
|
+
cursor.execute("""
|
|
179
|
+
INSERT OR REPLACE INTO modules (
|
|
180
|
+
module_name, status, version, description, author,
|
|
181
|
+
dependencies, optional_dependencies, pip_dependencies
|
|
182
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
183
|
+
""", (
|
|
184
|
+
module_name,
|
|
185
|
+
int(module_info.get('status', True)),
|
|
186
|
+
module_info.get('info', {}).get('version', ''),
|
|
187
|
+
module_info.get('info', {}).get('description', ''),
|
|
188
|
+
module_info.get('info', {}).get('author', ''),
|
|
189
|
+
json.dumps(module_info.get('info', {}).get('dependencies', [])),
|
|
190
|
+
json.dumps(module_info.get('info', {}).get('optional_dependencies', [])),
|
|
191
|
+
json.dumps(module_info.get('info', {}).get('pip_dependencies', [])) # 新增
|
|
192
|
+
))
|
|
193
|
+
conn.commit()
|
|
194
|
+
|
|
195
|
+
def update_module(self, module_name, module_info):
|
|
196
|
+
self.set_module(module_name, module_info)
|
|
197
|
+
|
|
198
|
+
def remove_module(self, module_name):
|
|
199
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
200
|
+
cursor = conn.cursor()
|
|
201
|
+
cursor.execute("DELETE FROM modules WHERE module_name = ?", (module_name,))
|
|
202
|
+
conn.commit()
|
|
203
|
+
return cursor.rowcount > 0
|
|
204
|
+
|
|
205
|
+
def __getattr__(self, key):
|
|
206
|
+
try:
|
|
207
|
+
return self.get(key)
|
|
208
|
+
except KeyError:
|
|
209
|
+
raise AttributeError(f"配置项 {key} 不存在")
|
|
210
|
+
|
|
211
|
+
env = EnvManager()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class CycleDependencyError(Exception):
|
|
2
|
+
def __init__(self, message):
|
|
3
|
+
self.message = message
|
|
4
|
+
super().__init__(self.message)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class InvalidDependencyError(Exception):
|
|
8
|
+
def __init__(self, message):
|
|
9
|
+
self.message = message
|
|
10
|
+
super().__init__(self.message)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InvalidModuleError(Exception):
|
|
14
|
+
def __init__(self, message):
|
|
15
|
+
self.message = message
|
|
16
|
+
super().__init__(self.message)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import inspect
|
|
3
|
+
from rich.logging import RichHandler
|
|
4
|
+
from . import sdk
|
|
5
|
+
from .envManager import env
|
|
6
|
+
|
|
7
|
+
_logger = logging.getLogger("RyhBot")
|
|
8
|
+
_log_level = env.get("LOG_LEVEL", "DEBUG")
|
|
9
|
+
if _log_level is None:
|
|
10
|
+
_log_level = logging.DEBUG
|
|
11
|
+
_logger.setLevel(_log_level)
|
|
12
|
+
|
|
13
|
+
if not _logger.handlers:
|
|
14
|
+
# 自定义时间格式
|
|
15
|
+
console_handler = RichHandler(
|
|
16
|
+
show_time=True, # 显示时间
|
|
17
|
+
show_level=True, # 显示日志级别
|
|
18
|
+
show_path=False, # 不显示调用路径
|
|
19
|
+
markup=True, # 支持 Markdown
|
|
20
|
+
log_time_format="[%Y-%m-%d %H:%M:%S]", # 自定义时间格式
|
|
21
|
+
rich_tracebacks=True, # 支持更美观的异常堆栈
|
|
22
|
+
)
|
|
23
|
+
console_handler.setFormatter(logging.Formatter("%(message)s"))
|
|
24
|
+
_logger.addHandler(console_handler)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _get_caller():
|
|
28
|
+
frame = inspect.currentframe().f_back.f_back
|
|
29
|
+
module = inspect.getmodule(frame)
|
|
30
|
+
module_name = module.__name__
|
|
31
|
+
if module_name == "__main__":
|
|
32
|
+
module_name = "Main"
|
|
33
|
+
if module_name.endswith(".Core"):
|
|
34
|
+
module_name = module_name[:-5]
|
|
35
|
+
return module_name
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def debug(msg, *args, **kwargs):
|
|
39
|
+
caller_module = _get_caller()
|
|
40
|
+
_logger.debug(f"[{caller_module}] {msg}", *args, **kwargs)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def info(msg, *args, **kwargs):
|
|
44
|
+
caller_module = _get_caller()
|
|
45
|
+
_logger.info(f"[{caller_module}] {msg}", *args, **kwargs)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def warning(msg, *args, **kwargs):
|
|
49
|
+
caller_module = _get_caller()
|
|
50
|
+
_logger.warning(f"[{caller_module}] {msg}", *args, **kwargs)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def error(msg, *args, **kwargs):
|
|
54
|
+
caller_module = _get_caller()
|
|
55
|
+
_logger.error(f"[{caller_module}] {msg}", *args, **kwargs)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def critical(msg, *args, **kwargs):
|
|
59
|
+
caller_module = _get_caller()
|
|
60
|
+
_logger.critical(f"[{caller_module}] {msg}", *args, **kwargs)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import aiohttp
|
|
3
|
+
from .envManager import env
|
|
4
|
+
|
|
5
|
+
class OriginManager:
|
|
6
|
+
def __init__(self):
|
|
7
|
+
self._init_origins()
|
|
8
|
+
|
|
9
|
+
def _init_origins(self):
|
|
10
|
+
if not env.get('origins'):
|
|
11
|
+
env.set('origins', [])
|
|
12
|
+
|
|
13
|
+
async def _validate_url(self, url):
|
|
14
|
+
if not url.startswith(('http://', 'https://')):
|
|
15
|
+
protocol = input("未指定协议,请输入使用的协议 (http 或 https): ").strip().lower()
|
|
16
|
+
if protocol not in ['http', 'https']:
|
|
17
|
+
print("无效的协议类型,必须是 http 或 https。")
|
|
18
|
+
return None
|
|
19
|
+
url = f"{protocol}://{url}"
|
|
20
|
+
|
|
21
|
+
if not url.endswith('.json'):
|
|
22
|
+
url = f"{url}/map.json"
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
async with aiohttp.ClientSession() as session:
|
|
26
|
+
async with session.get(url) as response:
|
|
27
|
+
response.raise_for_status()
|
|
28
|
+
if response.headers.get('Content-Type', '').startswith('application/json'):
|
|
29
|
+
return url
|
|
30
|
+
else:
|
|
31
|
+
print(f"源 {url} 返回的内容不是有效的 JSON 格式。")
|
|
32
|
+
return None
|
|
33
|
+
except Exception as e:
|
|
34
|
+
print(f"访问源 {url} 失败: {e}")
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def add_origin(self, value):
|
|
39
|
+
validated_url = asyncio.run(self._validate_url(value))
|
|
40
|
+
if not validated_url:
|
|
41
|
+
print("提供的源不是一个有效源,请检查后重试。")
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
origins = env.get('origins')
|
|
45
|
+
if validated_url not in origins:
|
|
46
|
+
origins.append(validated_url)
|
|
47
|
+
env.set('origins', origins)
|
|
48
|
+
print(f"源 {validated_url} 已成功添加。")
|
|
49
|
+
else:
|
|
50
|
+
print(f"源 {validated_url} 已存在,无需重复添加。")
|
|
51
|
+
|
|
52
|
+
def update_origins(self):
|
|
53
|
+
origins = env.get('origins')
|
|
54
|
+
providers = {}
|
|
55
|
+
modules = {}
|
|
56
|
+
module_alias = {}
|
|
57
|
+
|
|
58
|
+
print("\033[1m{:<10} | {:<20} | {:<50}\033[0m".format("源", "模块", "地址"))
|
|
59
|
+
print("-" * 85)
|
|
60
|
+
|
|
61
|
+
async def fetch_origin_data():
|
|
62
|
+
async with aiohttp.ClientSession() as session:
|
|
63
|
+
for origin in origins:
|
|
64
|
+
print(f"\n\033[94m正在获取 {origin}\033[0m")
|
|
65
|
+
try:
|
|
66
|
+
async with session.get(origin) as response:
|
|
67
|
+
response.raise_for_status()
|
|
68
|
+
if response.headers.get('Content-Type', '').startswith('application/json'):
|
|
69
|
+
content = await response.json()
|
|
70
|
+
providers[content["name"]] = content["base"]
|
|
71
|
+
|
|
72
|
+
for module in list(content["modules"].keys()):
|
|
73
|
+
module_content = content["modules"][module]
|
|
74
|
+
modules[f'{module}@{content["name"]}'] = module_content
|
|
75
|
+
module_origin_name = module_content["path"]
|
|
76
|
+
module_alias_name = module
|
|
77
|
+
module_alias[f'{module_origin_name}@{content["name"]}'] = module_alias_name
|
|
78
|
+
|
|
79
|
+
print("{:<10} | {:<20} | {:<50}".format(
|
|
80
|
+
content['name'],
|
|
81
|
+
module,
|
|
82
|
+
f"{providers[content['name']]}{module_origin_name}"
|
|
83
|
+
))
|
|
84
|
+
else:
|
|
85
|
+
print(f"\033[91m源 {origin} 返回的内容不是有效的 JSON 格式。\033[0m")
|
|
86
|
+
except Exception as e:
|
|
87
|
+
print(f"\033[91m获取 {origin} 时出错: {e}\033[0m")
|
|
88
|
+
|
|
89
|
+
# 使用 asyncio.run 调用异步任务
|
|
90
|
+
asyncio.run(fetch_origin_data())
|
|
91
|
+
|
|
92
|
+
env.set('providers', providers)
|
|
93
|
+
env.set('modules', modules)
|
|
94
|
+
env.set('module_alias', module_alias)
|
|
95
|
+
|
|
96
|
+
print("\n\033[92m{}\033[0m".format("完成".center(85, "-")))
|
|
97
|
+
|
|
98
|
+
def list_origins(self):
|
|
99
|
+
origins = env.get('origins')
|
|
100
|
+
for origin in origins:
|
|
101
|
+
print(origin)
|
|
102
|
+
|
|
103
|
+
def del_origin(self, value):
|
|
104
|
+
origins = env.get('origins')
|
|
105
|
+
if value in origins:
|
|
106
|
+
origins.remove(value)
|
|
107
|
+
env.set('origins', origins)
|
|
108
|
+
print(f"源 {value} 已删除。")
|
|
109
|
+
else:
|
|
110
|
+
print(f"源 {value} 不存在。")
|
|
111
|
+
|
|
112
|
+
origin_manager = OriginManager()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from collections import defaultdict, deque
|
|
2
|
+
import asyncio
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
+
|
|
5
|
+
executor = ThreadPoolExecutor()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def topological_sort(elements, dependencies, error):
|
|
9
|
+
graph = defaultdict(list)
|
|
10
|
+
in_degree = {element: 0 for element in elements}
|
|
11
|
+
for element, deps in dependencies.items():
|
|
12
|
+
for dep in deps:
|
|
13
|
+
graph[dep].append(element)
|
|
14
|
+
in_degree[element] += 1
|
|
15
|
+
queue = deque([element for element in elements if in_degree[element] == 0])
|
|
16
|
+
sorted_list = []
|
|
17
|
+
while queue:
|
|
18
|
+
node = queue.popleft()
|
|
19
|
+
sorted_list.append(node)
|
|
20
|
+
for neighbor in graph[node]:
|
|
21
|
+
in_degree[neighbor] -= 1
|
|
22
|
+
if in_degree[neighbor] == 0:
|
|
23
|
+
queue.append(neighbor)
|
|
24
|
+
if len(sorted_list) != len(elements):
|
|
25
|
+
raise error(f"Cycle detected in the dependencies: {elements} -> {dependencies}")
|
|
26
|
+
return sorted_list
|
|
27
|
+
|
|
28
|
+
def ExecAsync(async_func, *args, **kwargs):
|
|
29
|
+
loop = asyncio.get_event_loop()
|
|
30
|
+
return loop.run_in_executor(executor, lambda: asyncio.run(async_func(*args, **kwargs)))
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ErisPulse
|
|
3
|
+
Version: 1.0.7
|
|
4
|
+
Summary: ErisPulse 是一个模块化、可扩展的异步 Python SDK 框架,主要用于构建高效、可维护的机器人应用程序。
|
|
5
|
+
Home-page: https://github.com/wsu2059q/ErisPulse
|
|
6
|
+
Author: 艾莉丝·格雷拉特(WSu2059)
|
|
7
|
+
Author-email: wsu2059@qq.com
|
|
8
|
+
Maintainer: runoneall
|
|
9
|
+
Maintainer-email: runoobsteve@gmail.com
|
|
10
|
+
License: MIT
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
Classifier: Operating System :: OS Independent
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Python: >=3.7
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
Requires-Dist: aiohttp
|
|
27
|
+
Requires-Dist: rich
|
|
28
|
+
Dynamic: author
|
|
29
|
+
Dynamic: author-email
|
|
30
|
+
Dynamic: classifier
|
|
31
|
+
Dynamic: description
|
|
32
|
+
Dynamic: description-content-type
|
|
33
|
+
Dynamic: home-page
|
|
34
|
+
Dynamic: license
|
|
35
|
+
Dynamic: maintainer
|
|
36
|
+
Dynamic: maintainer-email
|
|
37
|
+
Dynamic: requires-dist
|
|
38
|
+
Dynamic: requires-python
|
|
39
|
+
Dynamic: summary
|
|
40
|
+
|
|
41
|
+
# ErisPulse
|
|
42
|
+
|
|
43
|
+
本项目基于 [RyhBotPythonSDK V2](https://github.com/runoneall/RyhBotPythonSDK2) 构建,并由 [sdkFrame](https://github.com/runoneall/sdkFrame) 提供支持。这是一个异步版本的 SDK,可能在功能和特性上与原库存在一定差异。
|
|
44
|
+
|
|
45
|
+
ErisPulse 是一个模块化、可扩展的异步 Python SDK 框架,主要用于构建高效、可维护的机器人应用程序。
|
|
46
|
+
|
|
47
|
+
# 更新日志
|
|
48
|
+
## 1.0.4
|
|
49
|
+
修复了部分命令行不支持logger颜色代码的问题 | 替换为rich
|
|
50
|
+
|
|
51
|
+
## 1.0.5
|
|
52
|
+
更新了SDK 模块对于pip依赖安装的支持
|
|
53
|
+
|
|
54
|
+
## 1.0.6
|
|
55
|
+
修复了SDK-CLI中的颜色乱码问题,并将db调整为包内存储,以解决多进程问题
|
|
56
|
+
|
|
57
|
+
## 1.0.7
|
|
58
|
+
修复诸多小问题
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
ErisPulse/__init__.py
|
|
4
|
+
ErisPulse/__main__.py
|
|
5
|
+
ErisPulse/envManager.py
|
|
6
|
+
ErisPulse/errors.py
|
|
7
|
+
ErisPulse/logger.py
|
|
8
|
+
ErisPulse/origin.py
|
|
9
|
+
ErisPulse/sdk.py
|
|
10
|
+
ErisPulse/util.py
|
|
11
|
+
ErisPulse.egg-info/PKG-INFO
|
|
12
|
+
ErisPulse.egg-info/SOURCES.txt
|
|
13
|
+
ErisPulse.egg-info/dependency_links.txt
|
|
14
|
+
ErisPulse.egg-info/requires.txt
|
|
15
|
+
ErisPulse.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ErisPulse
|
erispulse-1.0.7/PKG-INFO
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ErisPulse
|
|
3
|
+
Version: 1.0.7
|
|
4
|
+
Summary: ErisPulse 是一个模块化、可扩展的异步 Python SDK 框架,主要用于构建高效、可维护的机器人应用程序。
|
|
5
|
+
Home-page: https://github.com/wsu2059q/ErisPulse
|
|
6
|
+
Author: 艾莉丝·格雷拉特(WSu2059)
|
|
7
|
+
Author-email: wsu2059@qq.com
|
|
8
|
+
Maintainer: runoneall
|
|
9
|
+
Maintainer-email: runoobsteve@gmail.com
|
|
10
|
+
License: MIT
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
Classifier: Operating System :: OS Independent
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Python: >=3.7
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
Requires-Dist: aiohttp
|
|
27
|
+
Requires-Dist: rich
|
|
28
|
+
Dynamic: author
|
|
29
|
+
Dynamic: author-email
|
|
30
|
+
Dynamic: classifier
|
|
31
|
+
Dynamic: description
|
|
32
|
+
Dynamic: description-content-type
|
|
33
|
+
Dynamic: home-page
|
|
34
|
+
Dynamic: license
|
|
35
|
+
Dynamic: maintainer
|
|
36
|
+
Dynamic: maintainer-email
|
|
37
|
+
Dynamic: requires-dist
|
|
38
|
+
Dynamic: requires-python
|
|
39
|
+
Dynamic: summary
|
|
40
|
+
|
|
41
|
+
# ErisPulse
|
|
42
|
+
|
|
43
|
+
本项目基于 [RyhBotPythonSDK V2](https://github.com/runoneall/RyhBotPythonSDK2) 构建,并由 [sdkFrame](https://github.com/runoneall/sdkFrame) 提供支持。这是一个异步版本的 SDK,可能在功能和特性上与原库存在一定差异。
|
|
44
|
+
|
|
45
|
+
ErisPulse 是一个模块化、可扩展的异步 Python SDK 框架,主要用于构建高效、可维护的机器人应用程序。
|
|
46
|
+
|
|
47
|
+
# 更新日志
|
|
48
|
+
## 1.0.4
|
|
49
|
+
修复了部分命令行不支持logger颜色代码的问题 | 替换为rich
|
|
50
|
+
|
|
51
|
+
## 1.0.5
|
|
52
|
+
更新了SDK 模块对于pip依赖安装的支持
|
|
53
|
+
|
|
54
|
+
## 1.0.6
|
|
55
|
+
修复了SDK-CLI中的颜色乱码问题,并将db调整为包内存储,以解决多进程问题
|
|
56
|
+
|
|
57
|
+
## 1.0.7
|
|
58
|
+
修复诸多小问题
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# ErisPulse
|
|
2
|
+
|
|
3
|
+
本项目基于 [RyhBotPythonSDK V2](https://github.com/runoneall/RyhBotPythonSDK2) 构建,并由 [sdkFrame](https://github.com/runoneall/sdkFrame) 提供支持。这是一个异步版本的 SDK,可能在功能和特性上与原库存在一定差异。
|
|
4
|
+
|
|
5
|
+
ErisPulse 是一个模块化、可扩展的异步 Python SDK 框架,主要用于构建高效、可维护的机器人应用程序。
|
|
6
|
+
|
|
7
|
+
# 更新日志
|
|
8
|
+
## 1.0.4
|
|
9
|
+
修复了部分命令行不支持logger颜色代码的问题 | 替换为rich
|
|
10
|
+
|
|
11
|
+
## 1.0.5
|
|
12
|
+
更新了SDK 模块对于pip依赖安装的支持
|
|
13
|
+
|
|
14
|
+
## 1.0.6
|
|
15
|
+
修复了SDK-CLI中的颜色乱码问题,并将db调整为包内存储,以解决多进程问题
|
|
16
|
+
|
|
17
|
+
## 1.0.7
|
|
18
|
+
修复诸多小问题
|
erispulse-1.0.7/setup.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name='ErisPulse',
|
|
5
|
+
version='1.0.7',
|
|
6
|
+
author='艾莉丝·格雷拉特(WSu2059)',
|
|
7
|
+
author_email='wsu2059@qq.com',
|
|
8
|
+
maintainer='runoneall',
|
|
9
|
+
maintainer_email='runoobsteve@gmail.com',
|
|
10
|
+
description='ErisPulse 是一个模块化、可扩展的异步 Python SDK 框架,主要用于构建高效、可维护的机器人应用程序。',
|
|
11
|
+
long_description=open('README.md', encoding='utf-8').read(),
|
|
12
|
+
long_description_content_type='text/markdown',
|
|
13
|
+
url='https://github.com/wsu2059q/ErisPulse',
|
|
14
|
+
packages=find_packages(),
|
|
15
|
+
install_requires=["aiohttp", "rich"],
|
|
16
|
+
license='MIT',
|
|
17
|
+
classifiers=[
|
|
18
|
+
'Development Status :: 5 - Production/Stable',
|
|
19
|
+
'Intended Audience :: Developers',
|
|
20
|
+
'License :: OSI Approved :: MIT License',
|
|
21
|
+
'Programming Language :: Python :: 3',
|
|
22
|
+
'Programming Language :: Python :: 3.7',
|
|
23
|
+
'Programming Language :: Python :: 3.8',
|
|
24
|
+
'Programming Language :: Python :: 3.9',
|
|
25
|
+
'Programming Language :: Python :: 3.10',
|
|
26
|
+
'Programming Language :: Python :: 3.11',
|
|
27
|
+
'Programming Language :: Python :: 3.12',
|
|
28
|
+
'Programming Language :: Python :: 3 :: Only', # 仅支持 Python 3
|
|
29
|
+
'Operating System :: OS Independent',
|
|
30
|
+
'Topic :: Software Development :: Libraries :: Python Modules',
|
|
31
|
+
],
|
|
32
|
+
python_requires='>=3.7',
|
|
33
|
+
)
|