ErisPulse 2.4.2.dev0__tar.gz → 2.4.2.dev1__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-2.4.2.dev0 → erispulse-2.4.2.dev1}/PKG-INFO +1 -1
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/pyproject.toml +1 -1
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/adapter.py +141 -125
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/adapter.pyi +11 -3
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/config.py +89 -39
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/config.pyi +17 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/module.py +30 -19
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/module.pyi +18 -5
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/router.py +8 -3
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/loaders/module.py +12 -4
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/sdk.py +85 -53
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/sdk.pyi +39 -8
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/.gitignore +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/LICENSE +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/README.md +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/__init__.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/__init__.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/base.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/base.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/cli.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/cli.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/__init__.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/__init__.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/init.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/init.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/install.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/install.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/list.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/list.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/list_remote.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/list_remote.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/run.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/run.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/self_update.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/self_update.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/uninstall.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/uninstall.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/upgrade.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/commands/upgrade.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/console.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/console.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/registry.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/registry.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/utils/__init__.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/utils/__init__.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/utils/package_manager.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/CLI/utils/package_manager.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Bases/__init__.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Bases/__init__.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Bases/adapter.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Bases/adapter.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Bases/manager.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Bases/manager.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Bases/module.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Bases/module.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/__init__.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/__init__.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/base.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/base.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/command.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/command.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/message.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/message.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/message_builder.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/message_builder.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/meta.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/meta.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/notice.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/notice.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/request.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/request.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/session_type.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/session_type.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/wrapper.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/Event/wrapper.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/__init__.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/__init__.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/lifecycle.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/lifecycle.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/logger.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/logger.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/router.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/storage.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/Core/storage.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/__init__.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/__init__.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/__main__.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/__main__.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/finders/__init__.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/finders/__init__.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/finders/adapter.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/finders/adapter.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/finders/bases/__init__.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/finders/bases/__init__.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/finders/bases/finder.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/finders/bases/finder.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/finders/module.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/finders/module.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/loaders/__init__.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/loaders/__init__.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/loaders/adapter.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/loaders/adapter.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/loaders/bases/__init__.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/loaders/bases/__init__.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/loaders/bases/loader.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/loaders/bases/loader.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/loaders/module.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/loaders/strategy.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/loaders/strategy.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/runtime/__init__.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/runtime/__init__.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/runtime/cleanup.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/runtime/cleanup.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/runtime/exceptions.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/runtime/exceptions.pyi +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/runtime/frame_config.py +0 -0
- {erispulse-2.4.2.dev0 → erispulse-2.4.2.dev1}/src/ErisPulse/runtime/frame_config.pyi +0 -0
|
@@ -51,6 +51,9 @@ class AdapterManager(ManagerBase):
|
|
|
51
51
|
# Bot状态存储 - {platform: {bot_id: {"status": str, "last_active": float, "info": dict}}}
|
|
52
52
|
self._bots: dict[str, dict[str, dict]] = {}
|
|
53
53
|
|
|
54
|
+
# 标记是否正在关闭,避免重复提交离线事件
|
|
55
|
+
self._is_being_shutdown = False
|
|
56
|
+
|
|
54
57
|
def set_sdk_ref(self, sdk) -> bool:
|
|
55
58
|
"""
|
|
56
59
|
设置 SDK 引用
|
|
@@ -287,127 +290,134 @@ class AdapterManager(ManagerBase):
|
|
|
287
290
|
>>> # 关闭多个适配器
|
|
288
291
|
>>> await adapter.shutdown(["Platform1", "Platform2"])
|
|
289
292
|
"""
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if not isinstance(platforms, list):
|
|
293
|
-
platforms = [platforms]
|
|
294
|
-
for platform in platforms:
|
|
295
|
-
if platform not in self._adapters:
|
|
296
|
-
raise ValueError(f"平台 {platform} 未注册")
|
|
297
|
-
|
|
298
|
-
logger.info(f"关闭适配器 {platforms}")
|
|
299
|
-
|
|
300
|
-
# 提交适配器关闭开始事件
|
|
301
|
-
await lifecycle.submit_event(
|
|
302
|
-
"adapter.stop", msg="开始关闭适配器", data={"platforms": platforms}
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
from .router import router
|
|
306
|
-
|
|
307
|
-
# 需要收集受影响的 adapter 实例(因为多个平台可能共享同一个实例)
|
|
308
|
-
affected_adapters = set()
|
|
309
|
-
bots_to_offline = [] # [(platform, bot_id), ...]
|
|
293
|
+
# 设置关闭标志,避免重复提交离线事件
|
|
294
|
+
self._is_being_shutdown = True
|
|
310
295
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
adapter_instance = self._adapters[platform]
|
|
320
|
-
affected_adapters.add(adapter_instance)
|
|
321
|
-
|
|
322
|
-
# 收集该平台下需要标记为离线的 Bot
|
|
323
|
-
if platform in self._bots:
|
|
324
|
-
for bot_id, bot_info in self._bots[platform].items():
|
|
325
|
-
if bot_info.get("status") != "offline":
|
|
326
|
-
bots_to_offline.append((platform, bot_id))
|
|
296
|
+
try:
|
|
297
|
+
if platforms is None:
|
|
298
|
+
platforms = list(self._adapters.keys())
|
|
299
|
+
if not isinstance(platforms, list):
|
|
300
|
+
platforms = [platforms]
|
|
301
|
+
for platform in platforms:
|
|
302
|
+
if platform not in self._adapters:
|
|
303
|
+
raise ValueError(f"平台 {platform} 未注册")
|
|
327
304
|
|
|
328
|
-
|
|
329
|
-
for adapter_instance in affected_adapters:
|
|
330
|
-
if adapter_instance in self._started_instances:
|
|
331
|
-
# 找到该实例对应的平台名(用于事件提交)
|
|
332
|
-
instance_platforms = [
|
|
333
|
-
p for p, a in self._adapters.items() if a is adapter_instance
|
|
334
|
-
]
|
|
335
|
-
platform_label = (
|
|
336
|
-
instance_platforms[0]
|
|
337
|
-
if instance_platforms
|
|
338
|
-
else str(id(adapter_instance))
|
|
339
|
-
)
|
|
305
|
+
logger.info(f"关闭适配器 {platforms}")
|
|
340
306
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
"adapter.status.change",
|
|
346
|
-
msg=f"适配器 {p} 状态变化: stopping",
|
|
347
|
-
data={"platform": p, "status": "stopping"},
|
|
348
|
-
)
|
|
307
|
+
# 提交适配器关闭开始事件
|
|
308
|
+
await lifecycle.submit_event(
|
|
309
|
+
"adapter.stop", msg="开始关闭适配器", data={"platforms": platforms}
|
|
310
|
+
)
|
|
349
311
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
312
|
+
from .router import router
|
|
313
|
+
|
|
314
|
+
# 需要收集受影响的 adapter 实例(因为多个平台可能共享同一个实例)
|
|
315
|
+
affected_adapters = set()
|
|
316
|
+
bots_to_offline = [] # [(platform, bot_id), ...]
|
|
317
|
+
|
|
318
|
+
# 取消目标平台的后台启动任务
|
|
319
|
+
for platform in platforms:
|
|
320
|
+
task = self._adapter_tasks.pop(platform, None)
|
|
321
|
+
if task and not task.done():
|
|
322
|
+
task.cancel()
|
|
323
|
+
logger.debug(f"已取消平台 {platform} 的后台启动任务")
|
|
324
|
+
|
|
325
|
+
for platform in platforms:
|
|
326
|
+
adapter_instance = self._adapters[platform]
|
|
327
|
+
affected_adapters.add(adapter_instance)
|
|
328
|
+
|
|
329
|
+
# 收集该平台下需要标记为离线的 Bot
|
|
330
|
+
if platform in self._bots:
|
|
331
|
+
for bot_id, bot_info in self._bots[platform].items():
|
|
332
|
+
if bot_info.get("status") != "offline":
|
|
333
|
+
bots_to_offline.append((platform, bot_id))
|
|
334
|
+
|
|
335
|
+
# 对每个受影响的 adapter 实例执行 shutdown(如果尚未关闭)
|
|
336
|
+
for adapter_instance in affected_adapters:
|
|
337
|
+
if adapter_instance in self._started_instances:
|
|
338
|
+
# 找到该实例对应的平台名(用于事件提交)
|
|
339
|
+
instance_platforms = [
|
|
340
|
+
p for p, a in self._adapters.items() if a is adapter_instance
|
|
341
|
+
]
|
|
342
|
+
platform_label = (
|
|
343
|
+
instance_platforms[0]
|
|
344
|
+
if instance_platforms
|
|
345
|
+
else str(id(adapter_instance))
|
|
346
|
+
)
|
|
353
347
|
|
|
354
|
-
# 提交适配器状态变化事件(
|
|
348
|
+
# 提交适配器状态变化事件(stopping)
|
|
355
349
|
for p in instance_platforms:
|
|
356
350
|
if p in platforms:
|
|
357
351
|
await lifecycle.submit_event(
|
|
358
352
|
"adapter.status.change",
|
|
359
|
-
msg=f"适配器 {p} 状态变化:
|
|
360
|
-
data={"platform": p, "status": "
|
|
353
|
+
msg=f"适配器 {p} 状态变化: stopping",
|
|
354
|
+
data={"platform": p, "status": "stopping"},
|
|
361
355
|
)
|
|
362
|
-
except Exception as e:
|
|
363
|
-
logger.error(f"关闭适配器实例 {id(adapter_instance)} 失败: {e}")
|
|
364
356
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
"status"
|
|
374
|
-
"
|
|
375
|
-
|
|
376
|
-
|
|
357
|
+
try:
|
|
358
|
+
await adapter_instance.shutdown()
|
|
359
|
+
self._started_instances.remove(adapter_instance)
|
|
360
|
+
|
|
361
|
+
# 提交适配器状态变化事件(stopped)
|
|
362
|
+
for p in instance_platforms:
|
|
363
|
+
if p in platforms:
|
|
364
|
+
await lifecycle.submit_event(
|
|
365
|
+
"adapter.status.change",
|
|
366
|
+
msg=f"适配器 {p} 状态变化: stopped",
|
|
367
|
+
data={"platform": p, "status": "stopped"},
|
|
368
|
+
)
|
|
369
|
+
except Exception as e:
|
|
370
|
+
logger.error(f"关闭适配器实例 {id(adapter_instance)} 失败: {e}")
|
|
371
|
+
|
|
372
|
+
# 提交适配器状态变化事件(stop_failed)
|
|
373
|
+
for p in instance_platforms:
|
|
374
|
+
if p in platforms:
|
|
375
|
+
await lifecycle.submit_event(
|
|
376
|
+
"adapter.status.change",
|
|
377
|
+
msg=f"适配器 {p} 状态变化: stop_failed",
|
|
378
|
+
data={
|
|
379
|
+
"platform": p,
|
|
380
|
+
"status": "stop_failed",
|
|
381
|
+
"error": str(e),
|
|
382
|
+
},
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
# 清理被关闭平台的路由
|
|
386
|
+
for platform in platforms:
|
|
387
|
+
result = router.unregister_all_by_namespace(platform)
|
|
388
|
+
if result["http_count"] > 0 or result["websocket_count"] > 0:
|
|
389
|
+
logger.debug(
|
|
390
|
+
f"已清理平台 {platform} 的路由: HTTP={result['http_count']}, WebSocket={result['websocket_count']}"
|
|
391
|
+
)
|
|
377
392
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if result["http_count"] > 0 or result["websocket_count"] > 0:
|
|
382
|
-
logger.debug(
|
|
383
|
-
f"已清理平台 {platform} 的路由: HTTP={result['http_count']}, WebSocket={result['websocket_count']}"
|
|
384
|
-
)
|
|
393
|
+
# 停止路由器(仅当所有适配器都关闭时)
|
|
394
|
+
if not self._started_instances:
|
|
395
|
+
await router.stop()
|
|
385
396
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
"adapter.bot.offline",
|
|
397
|
-
msg=f"Bot {platform}/{bot_id} 离线",
|
|
398
|
-
data={"platform": platform, "bot_id": bot_id, "status": "offline"},
|
|
399
|
-
)
|
|
397
|
+
# 将相关 Bot 标记为离线
|
|
398
|
+
for platform, bot_id in bots_to_offline:
|
|
399
|
+
if platform in self._bots and bot_id in self._bots[platform]:
|
|
400
|
+
self._bots[platform][bot_id]["status"] = "offline"
|
|
401
|
+
# 提交 Bot 离线事件
|
|
402
|
+
await lifecycle.submit_event(
|
|
403
|
+
"adapter.bot.offline",
|
|
404
|
+
msg=f"Bot {platform}/{bot_id} 离线",
|
|
405
|
+
data={"platform": platform, "bot_id": bot_id, "status": "offline"},
|
|
406
|
+
)
|
|
400
407
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
408
|
+
# 如果所有适配器都关闭了,清理事件处理器
|
|
409
|
+
if not self._started_instances:
|
|
410
|
+
self._onebot_handlers.clear()
|
|
411
|
+
self._raw_handlers.clear()
|
|
412
|
+
self._onebot_middlewares.clear()
|
|
406
413
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
414
|
+
# 提交适配器关闭完成事件
|
|
415
|
+
await lifecycle.submit_event(
|
|
416
|
+
"adapter.stopped", msg="适配器关闭完成", data={"platforms": platforms}
|
|
417
|
+
)
|
|
418
|
+
finally:
|
|
419
|
+
# 清除关闭标志
|
|
420
|
+
self._is_being_shutdown = False
|
|
411
421
|
|
|
412
422
|
def clear(self) -> None:
|
|
413
423
|
"""
|
|
@@ -473,12 +483,11 @@ class AdapterManager(ManagerBase):
|
|
|
473
483
|
|
|
474
484
|
def exists(self, platform: str) -> bool:
|
|
475
485
|
"""
|
|
476
|
-
|
|
486
|
+
检查平台是否已注册
|
|
477
487
|
|
|
478
488
|
:param platform: 平台名称
|
|
479
|
-
:return:
|
|
489
|
+
:return: 平台是否已注册(即 adapter.register() 已被调用)
|
|
480
490
|
"""
|
|
481
|
-
# 检查平台是否已注册(在 _adapters 中)
|
|
482
491
|
return platform in self._adapters
|
|
483
492
|
|
|
484
493
|
def is_enabled(self, platform: str) -> bool:
|
|
@@ -486,20 +495,25 @@ class AdapterManager(ManagerBase):
|
|
|
486
495
|
检查平台适配器是否启用
|
|
487
496
|
|
|
488
497
|
:param platform: 平台名称
|
|
489
|
-
:return:
|
|
498
|
+
:return: 平台适配器是否启用
|
|
499
|
+
|
|
500
|
+
{!--< tips >!--}
|
|
501
|
+
适配器启用条件:
|
|
502
|
+
1. 适配器在配置文件中(ErisPulse.adapters.status.{platform} 存在)
|
|
503
|
+
2. 配置值为启用状态
|
|
504
|
+
|
|
505
|
+
如果适配器未在配置中,返回 False
|
|
506
|
+
{!--< /tips >!--}
|
|
490
507
|
"""
|
|
491
|
-
|
|
508
|
+
from .config import parse_bool_config
|
|
509
|
+
|
|
492
510
|
status = config.getConfig(f"ErisPulse.adapters.status.{platform}")
|
|
493
511
|
|
|
494
|
-
#
|
|
512
|
+
# 适配器未在配置中,返回 False
|
|
495
513
|
if status is None:
|
|
496
|
-
return False
|
|
497
|
-
|
|
498
|
-
# 处理字符串形式的布尔值
|
|
499
|
-
if isinstance(status, str):
|
|
500
|
-
return status.lower() not in ("false", "0", "no", "off")
|
|
514
|
+
return False
|
|
501
515
|
|
|
502
|
-
return
|
|
516
|
+
return parse_bool_config(status)
|
|
503
517
|
|
|
504
518
|
def enable(self, platform: str) -> bool:
|
|
505
519
|
"""
|
|
@@ -541,7 +555,7 @@ class AdapterManager(ManagerBase):
|
|
|
541
555
|
:return: 是否取消成功
|
|
542
556
|
|
|
543
557
|
{!--< internal-use >!--}
|
|
544
|
-
|
|
558
|
+
注意: 此方法仅取消注册, 不关闭已启动的适配器
|
|
545
559
|
{!--< /internal-use >!--}
|
|
546
560
|
"""
|
|
547
561
|
if platform not in self._adapters:
|
|
@@ -837,13 +851,15 @@ class AdapterManager(ManagerBase):
|
|
|
837
851
|
)
|
|
838
852
|
|
|
839
853
|
if status == "offline":
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
854
|
+
# 只有在非主动关闭的情况下才提交事件(避免与 shutdown() 重复)
|
|
855
|
+
if not self._is_being_shutdown:
|
|
856
|
+
asyncio.ensure_future(
|
|
857
|
+
lifecycle.submit_event(
|
|
858
|
+
"adapter.bot.offline",
|
|
859
|
+
msg=f"Bot {platform}/{bot_id} 离线",
|
|
860
|
+
data={"platform": platform, "bot_id": bot_id, "status": "offline"},
|
|
861
|
+
)
|
|
845
862
|
)
|
|
846
|
-
)
|
|
847
863
|
|
|
848
864
|
def _update_bot_heartbeat(self, platform: str, self_info: dict) -> None:
|
|
849
865
|
"""
|
|
@@ -104,10 +104,10 @@ class AdapterManager(ManagerBase):
|
|
|
104
104
|
...
|
|
105
105
|
def exists(self: object, platform: str) -> bool:
|
|
106
106
|
"""
|
|
107
|
-
|
|
107
|
+
检查平台是否已注册
|
|
108
108
|
|
|
109
109
|
:param platform: 平台名称
|
|
110
|
-
:return:
|
|
110
|
+
:return: 平台是否已注册(即 adapter.register() 已被调用)
|
|
111
111
|
"""
|
|
112
112
|
...
|
|
113
113
|
def is_enabled(self: object, platform: str) -> bool:
|
|
@@ -115,7 +115,15 @@ class AdapterManager(ManagerBase):
|
|
|
115
115
|
检查平台适配器是否启用
|
|
116
116
|
|
|
117
117
|
:param platform: 平台名称
|
|
118
|
-
:return:
|
|
118
|
+
:return: 平台适配器是否启用
|
|
119
|
+
|
|
120
|
+
{!--< tips >!--}
|
|
121
|
+
适配器启用条件:
|
|
122
|
+
1. 适配器在配置文件中(ErisPulse.adapters.status.{platform} 存在)
|
|
123
|
+
2. 配置值为启用状态
|
|
124
|
+
|
|
125
|
+
如果适配器未在配置中,返回 False
|
|
126
|
+
{!--< /tips >!--}
|
|
119
127
|
"""
|
|
120
128
|
...
|
|
121
129
|
def enable(self: object, platform: str) -> bool:
|
|
@@ -24,8 +24,9 @@ class ConfigManager:
|
|
|
24
24
|
self._cache_timestamp = 0 # 缓存时间戳
|
|
25
25
|
self._cache_timeout = 60 # 缓存超时时间(秒)
|
|
26
26
|
self._write_delay = 5 # 写入延迟(秒)
|
|
27
|
-
self._write_timer = None # 写入定时器
|
|
27
|
+
self._write_timer: threading.Timer | None = None # 写入定时器
|
|
28
28
|
self._lock = threading.RLock() # 线程安全锁
|
|
29
|
+
self._file_lock = threading.RLock() # 文件操作锁,确保原子性
|
|
29
30
|
self._migrate_config() # 迁移旧配置文件
|
|
30
31
|
self._load_config() # 初始化时加载配置
|
|
31
32
|
|
|
@@ -138,56 +139,79 @@ class ConfigManager:
|
|
|
138
139
|
def _flush_config(self) -> None:
|
|
139
140
|
"""
|
|
140
141
|
将待写入的配置刷新到文件
|
|
142
|
+
|
|
143
|
+
使用文件锁确保多线程环境下的原子性操作
|
|
141
144
|
"""
|
|
142
145
|
with self._lock:
|
|
143
146
|
if not self._dirty_keys:
|
|
144
147
|
return # 没有需要写入的内容
|
|
145
148
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
149
|
+
with self._file_lock: # 确保文件操作原子性
|
|
150
|
+
try:
|
|
151
|
+
# 从文件读取完整配置
|
|
152
|
+
if os.path.exists(self.CONFIG_FILE):
|
|
153
|
+
with open(self.CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
154
|
+
config = toml.load(f)
|
|
155
|
+
else:
|
|
156
|
+
config = {}
|
|
157
|
+
|
|
158
|
+
# 应用待写入的更改
|
|
159
|
+
for key, value in self._dirty_keys.items():
|
|
160
|
+
keys = key.split(".")
|
|
161
|
+
current = config
|
|
162
|
+
for k in keys[:-1]:
|
|
163
|
+
if k not in current:
|
|
164
|
+
current[k] = {}
|
|
165
|
+
current = current[k]
|
|
166
|
+
current[keys[-1]] = value
|
|
167
|
+
|
|
168
|
+
# 对配置字典进行排序,确保同一模块的配置项排列在一起
|
|
169
|
+
sorted_config = self._sort_config_dict(config)
|
|
170
|
+
|
|
171
|
+
# 写入临时文件,确保原子性
|
|
172
|
+
temp_file = self.CONFIG_FILE + ".tmp"
|
|
173
|
+
with open(temp_file, "w", encoding="utf-8") as f:
|
|
174
|
+
toml.dump(sorted_config, f)
|
|
175
|
+
|
|
176
|
+
# 原子性重命名(跨平台兼容)
|
|
177
|
+
if os.name == 'nt': # Windows
|
|
178
|
+
if os.path.exists(self.CONFIG_FILE):
|
|
179
|
+
os.replace(temp_file, self.CONFIG_FILE)
|
|
180
|
+
else:
|
|
181
|
+
os.rename(temp_file, self.CONFIG_FILE)
|
|
182
|
+
else: # Unix/Linux/macOS
|
|
183
|
+
os.rename(temp_file, self.CONFIG_FILE)
|
|
184
|
+
|
|
185
|
+
# 更新缓存并清除待写入队列
|
|
186
|
+
self._cache = sorted_config
|
|
187
|
+
self._cache_timestamp = time.time()
|
|
188
|
+
self._dirty_keys.clear()
|
|
175
189
|
|
|
176
|
-
|
|
177
|
-
|
|
190
|
+
except Exception as e:
|
|
191
|
+
from .logger import logger
|
|
178
192
|
|
|
179
|
-
|
|
193
|
+
logger.error(f"写入配置文件 {self.CONFIG_FILE} 失败: {e}")
|
|
194
|
+
# 清理临时文件
|
|
195
|
+
temp_file = self.CONFIG_FILE + ".tmp"
|
|
196
|
+
if os.path.exists(temp_file):
|
|
197
|
+
try:
|
|
198
|
+
os.remove(temp_file)
|
|
199
|
+
except Exception:
|
|
200
|
+
pass
|
|
180
201
|
|
|
181
202
|
def _schedule_write(self) -> None:
|
|
182
203
|
"""
|
|
183
204
|
安排延迟写入
|
|
205
|
+
|
|
206
|
+
线程安全:使用锁保护 Timer 的取消和创建
|
|
184
207
|
"""
|
|
185
|
-
|
|
186
|
-
self._write_timer
|
|
208
|
+
with self._lock:
|
|
209
|
+
if self._write_timer:
|
|
210
|
+
self._write_timer.cancel()
|
|
187
211
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
212
|
+
self._write_timer = threading.Timer(self._write_delay, self._flush_config)
|
|
213
|
+
self._write_timer.daemon = True
|
|
214
|
+
self._write_timer.start()
|
|
191
215
|
|
|
192
216
|
def _check_cache_validity(self) -> None:
|
|
193
217
|
"""
|
|
@@ -268,4 +292,30 @@ class ConfigManager:
|
|
|
268
292
|
|
|
269
293
|
config: ConfigManager = ConfigManager()
|
|
270
294
|
|
|
271
|
-
|
|
295
|
+
|
|
296
|
+
def parse_bool_config(value: Any) -> bool:
|
|
297
|
+
"""
|
|
298
|
+
解析配置中的布尔值
|
|
299
|
+
|
|
300
|
+
:param value: 配置值(可以是 bool, int, str 等)
|
|
301
|
+
:return: 解析后的布尔值
|
|
302
|
+
|
|
303
|
+
支持的值:
|
|
304
|
+
- True: True, 1, "true", "True", "1", "yes", "Yes", "on", "On"
|
|
305
|
+
- False: False, 0, "false", "False", "0", "no", "No", "off", "Off"
|
|
306
|
+
"""
|
|
307
|
+
if isinstance(value, bool):
|
|
308
|
+
return value
|
|
309
|
+
|
|
310
|
+
if isinstance(value, int):
|
|
311
|
+
return value != 0
|
|
312
|
+
|
|
313
|
+
if isinstance(value, str):
|
|
314
|
+
normalized = value.lower().strip()
|
|
315
|
+
return normalized in ("true", "1", "yes", "on")
|
|
316
|
+
|
|
317
|
+
# 其他类型尝试转换为布尔值
|
|
318
|
+
return bool(value)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
__all__ = ["config", "parse_bool_config"]
|
|
@@ -46,11 +46,15 @@ class ConfigManager:
|
|
|
46
46
|
def _flush_config(self: object) -> None:
|
|
47
47
|
"""
|
|
48
48
|
将待写入的配置刷新到文件
|
|
49
|
+
|
|
50
|
+
使用文件锁确保多线程环境下的原子性操作
|
|
49
51
|
"""
|
|
50
52
|
...
|
|
51
53
|
def _schedule_write(self: object) -> None:
|
|
52
54
|
"""
|
|
53
55
|
安排延迟写入
|
|
56
|
+
|
|
57
|
+
线程安全:使用锁保护 Timer 的取消和创建
|
|
54
58
|
"""
|
|
55
59
|
...
|
|
56
60
|
def _check_cache_validity(self: object) -> None:
|
|
@@ -87,3 +91,16 @@ class ConfigManager:
|
|
|
87
91
|
...
|
|
88
92
|
|
|
89
93
|
config: ConfigManager
|
|
94
|
+
|
|
95
|
+
def parse_bool_config(value: Any) -> bool:
|
|
96
|
+
"""
|
|
97
|
+
解析配置中的布尔值
|
|
98
|
+
|
|
99
|
+
:param value: 配置值(可以是 bool, int, str 等)
|
|
100
|
+
:return: 解析后的布尔值
|
|
101
|
+
|
|
102
|
+
支持的值:
|
|
103
|
+
- True: True, 1, "true", "True", "1", "yes", "Yes", "on", "On"
|
|
104
|
+
- False: False, 0, "false", "False", "0", "no", "No", "off", "Off"
|
|
105
|
+
"""
|
|
106
|
+
...
|
|
@@ -272,15 +272,17 @@ class ModuleManager(ManagerBase):
|
|
|
272
272
|
|
|
273
273
|
def exists(self, module_name: str) -> bool:
|
|
274
274
|
"""
|
|
275
|
-
|
|
275
|
+
检查模块是否已注册
|
|
276
276
|
|
|
277
|
-
:param module_name:
|
|
278
|
-
:return:
|
|
277
|
+
:param module_name: 模块名称
|
|
278
|
+
:return: 模块是否已注册(即 module.register() 已被调用)
|
|
279
|
+
|
|
280
|
+
{!--< tips >!--}
|
|
281
|
+
exists() 只检查模块类是否已注册到管理器,用于验证模块是否可以加载。
|
|
282
|
+
如需检查模块是否启用,请使用 is_enabled()。
|
|
283
|
+
{!--< /tips >!--}
|
|
279
284
|
"""
|
|
280
|
-
|
|
281
|
-
return True
|
|
282
|
-
module_statuses = config.getConfig("ErisPulse.modules.status", {})
|
|
283
|
-
return module_name in module_statuses
|
|
285
|
+
return module_name in self._module_classes
|
|
284
286
|
|
|
285
287
|
def is_loaded(self, module_name: str) -> bool:
|
|
286
288
|
"""
|
|
@@ -350,9 +352,9 @@ class ModuleManager(ManagerBase):
|
|
|
350
352
|
{!--< internal-use >!--}
|
|
351
353
|
此方法仅供内部使用
|
|
352
354
|
|
|
353
|
-
:param module_name:
|
|
354
|
-
:param enabled:
|
|
355
|
-
:return:
|
|
355
|
+
:param module_name: 模块名称
|
|
356
|
+
:param enabled: 是否启用模块 (默认: False)
|
|
357
|
+
:return: 操作是否成功
|
|
356
358
|
"""
|
|
357
359
|
if self.exists(module_name):
|
|
358
360
|
return True
|
|
@@ -367,18 +369,27 @@ class ModuleManager(ManagerBase):
|
|
|
367
369
|
"""
|
|
368
370
|
检查模块是否启用
|
|
369
371
|
|
|
370
|
-
:param module_name:
|
|
371
|
-
:return:
|
|
372
|
+
:param module_name: 模块名称
|
|
373
|
+
:return: 模块是否启用
|
|
374
|
+
|
|
375
|
+
{!--< tips >!--}
|
|
376
|
+
模块启用条件:
|
|
377
|
+
1. 模块在配置文件中(ErisPulse.modules.status.{module_name} 存在)
|
|
378
|
+
2. 配置值为启用状态
|
|
379
|
+
|
|
380
|
+
如果模块未在配置中,返回 False
|
|
381
|
+
{!--< /tips >!--}
|
|
372
382
|
"""
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
return False
|
|
383
|
+
from .config import parse_bool_config
|
|
384
|
+
|
|
385
|
+
status = config.getConfig(f"ErisPulse.modules.status.{module_name}")
|
|
377
386
|
|
|
378
|
-
|
|
379
|
-
|
|
387
|
+
# 模块未在配置中,返回 False
|
|
388
|
+
if status is None:
|
|
389
|
+
return False
|
|
380
390
|
|
|
381
|
-
|
|
391
|
+
# 解析配置值
|
|
392
|
+
return parse_bool_config(status)
|
|
382
393
|
|
|
383
394
|
def enable(self, module_name: str) -> bool:
|
|
384
395
|
"""
|