ErisPulse 2.10.0.dev0__tar.gz → 2.10.1__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.10.0.dev0 → erispulse-2.10.1}/PKG-INFO +2 -2
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/pyproject.toml +6 -2
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/commands/create.py +3 -18
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/constants.py +1 -1
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/converter.py +11 -2
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Event/base.py +28 -4
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Event/command.py +104 -50
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Event/interaction.py +48 -16
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Event/message.py +12 -4
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Event/message_builder.py +7 -1
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Event/overrides.py +7 -3
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Event/wrapper.py +116 -33
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/config.py +27 -9
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/constants.py +8 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/i18n/locales/en.py +6 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/i18n/locales/ja.py +6 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/i18n/locales/ru.py +6 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/i18n/locales/zh_cn.py +6 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/i18n/locales/zh_tw.py +6 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/router.py +41 -4
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/runtime/sdk_initializer.py +17 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/.gitignore +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/LICENSE +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/README.pypi.md +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/base.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/cli.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/commands/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/commands/config.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/commands/doctor.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/commands/init.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/commands/install.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/commands/language.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/commands/list.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/commands/list_remote.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/commands/run.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/commands/self_update.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/commands/types.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/commands/uninstall.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/commands/upgrade.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/commands/version.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/console.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/hints.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/i18n/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/i18n/locales/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/i18n/locales/en.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/i18n/locales/ja.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/i18n/locales/ru.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/i18n/locales/zh_cn.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/i18n/locales/zh_tw.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/registry.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/utils/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/utils/config_wizard.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/utils/display.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/utils/interactive.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/utils/package_manager.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/CLI/utils/scaffold_text.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/adapter.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/api_dsl.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/client.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/config_schema.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/errors.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/i18n_schema.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/kv_builder.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/manager.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/model.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/module.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/router.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/send_builder.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/send_rules.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/sql_base.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/storage.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Bases/websocket.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Event/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Event/command_args.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Event/conversation.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Event/governance.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Event/meta.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Event/notice.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Event/request.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Event/session_type.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Event/throttle.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/Event/trace.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/adapter.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/assets/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/assets/error.css +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/assets/error.html +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/assets/root.css +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/assets/root.html +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/client.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/connections.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/di.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/i18n/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/i18n/constants.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/i18n/locales/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/lifecycle.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/logger.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/master.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/module.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/ownership.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/scope.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/shadow.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/storage/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/storage/mysql.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/storage/postgres.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/storage/sqlite.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/text_match.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/Core/transcript.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/__main__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/finders/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/finders/adapter.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/finders/bases/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/finders/bases/finder.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/finders/module.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/loaders/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/loaders/adapter.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/loaders/bases/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/loaders/bases/loader.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/loaders/module.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/loaders/plugin_folder.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/loaders/strategy.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/loaders/strict.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/py.typed +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/runtime/__init__.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/runtime/config_schema.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/runtime/context.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/runtime/diagnostics.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/runtime/example_config.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/runtime/exceptions.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/runtime/file_watcher.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/runtime/frame_config.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/runtime/hints.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/runtime/memory.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/runtime/owner_cleanup.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/runtime/plugin_reload.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/runtime/proactive_gc.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/runtime/tasks.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/runtime/troubleshoot.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/runtime/version.py +0 -0
- {erispulse-2.10.0.dev0 → erispulse-2.10.1}/src/ErisPulse/sdk.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ErisPulse
|
|
3
|
-
Version: 2.10.
|
|
3
|
+
Version: 2.10.1
|
|
4
4
|
Summary: Event-driven modular async bot framework — write once, deploy to any platform | 事件驱动的模块化异步机器人框架,一次编写,多平台部署
|
|
5
5
|
Project-URL: Homepage, https://github.com/ErisPulse/ErisPulse
|
|
6
6
|
Project-URL: Documentation, https://www.erisdev.com#docs
|
|
@@ -34,7 +34,7 @@ Requires-Dist: aiosqlite>=0.20.0
|
|
|
34
34
|
Requires-Dist: fastapi>=0.116.1
|
|
35
35
|
Requires-Dist: rich>=13.0.0
|
|
36
36
|
Requires-Dist: tomlkit>=0.15.1
|
|
37
|
-
Requires-Dist: uvicorn
|
|
37
|
+
Requires-Dist: uvicorn<1.0,>=0.35.0
|
|
38
38
|
Requires-Dist: websockets>=12.0
|
|
39
39
|
Provides-Extra: mysql
|
|
40
40
|
Requires-Dist: aiomysql>=0.2.0; extra == 'mysql'
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ErisPulse"
|
|
7
|
-
version = "2.10.
|
|
7
|
+
version = "2.10.1"
|
|
8
8
|
description = "Event-driven modular async bot framework — write once, deploy to any platform | 事件驱动的模块化异步机器人框架,一次编写,多平台部署"
|
|
9
9
|
readme = "README.pypi.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -67,7 +67,11 @@ dependencies = [
|
|
|
67
67
|
"websockets>=12.0",
|
|
68
68
|
"aiohttp>=3.9.0",
|
|
69
69
|
"fastapi>=0.116.1",
|
|
70
|
-
|
|
70
|
+
|
|
71
|
+
# uvicorn的内嵌启动依赖私有接口 Server._serve()(0.34.0 引入,用于跳过 serve() 的信号捕获),
|
|
72
|
+
# 上限锁定 0.x 线;启动期按能力探测,接口缺失时仅告警并回退公开 serve()
|
|
73
|
+
"uvicorn>=0.35.0,<1.0",
|
|
74
|
+
|
|
71
75
|
# storage 相关(默认 SQLite 后端异步驱动,开箱即用)
|
|
72
76
|
"aiosqlite>=0.20.0",
|
|
73
77
|
]
|
|
@@ -42,10 +42,10 @@ SOFTWARE.
|
|
|
42
42
|
"""
|
|
43
43
|
|
|
44
44
|
# 生成模块/适配器模板的最低 SDK 地板(写入模板 min_sdk_version 字段)。
|
|
45
|
-
#
|
|
46
|
-
#
|
|
45
|
+
# 模板示例使用根导入契约(2.10 特性)与 args=/options=(2.9 特性);pip 默认不解析
|
|
46
|
+
# 预发布版本,该地板不得高于已发布的正式版本,否则生成的组件会因依赖装不上或
|
|
47
47
|
# 加载器按 PEP 440 拒载(与 CLI/constants.py 的 INIT_ERISPULSE_DEPENDENCY 同步升版)。
|
|
48
|
-
_TEMPLATE_MIN_SDK_VERSION = "2.
|
|
48
|
+
_TEMPLATE_MIN_SDK_VERSION = "2.10.0"
|
|
49
49
|
|
|
50
50
|
_MODULE_PYPROJECT = """[project]
|
|
51
51
|
name = "ErisPulse-{name}"
|
|
@@ -107,12 +107,6 @@ class Main(BaseModule):
|
|
|
107
107
|
:ivar advanced_threshold: 高级阈值(docstring :ivar: 声明即为字段描述,无需 metadata)
|
|
108
108
|
\"\"\"
|
|
109
109
|
|
|
110
|
-
enabled: bool = field(
|
|
111
|
-
default=True,
|
|
112
|
-
metadata={{
|
|
113
|
-
\"description\": {{\"i18n\": \"module.{name}.enabled\", \"default\": \"Enable module\"}},
|
|
114
|
-
}},
|
|
115
|
-
)
|
|
116
110
|
# example 字段:默认不写入 config.toml(仅记录在 config.full.example,用户按需启用)
|
|
117
111
|
advanced_threshold: int = field(
|
|
118
112
|
default=100,
|
|
@@ -132,15 +126,6 @@ class Main(BaseModule):
|
|
|
132
126
|
ru=\"Модуль {name}\",
|
|
133
127
|
zh_TW=\"{name} 模組\",
|
|
134
128
|
)
|
|
135
|
-
enabled: I18nKey = I18nKey(
|
|
136
|
-
key=\"module.{name}.enabled\",
|
|
137
|
-
default=\"Enable module\",
|
|
138
|
-
zh_CN=\"是否启用模块\",
|
|
139
|
-
en=\"Enable module\",
|
|
140
|
-
ja=\"モジュールを有効にする\",
|
|
141
|
-
ru=\"Включить модуль\",
|
|
142
|
-
zh_TW=\"啟用模組\",
|
|
143
|
-
)
|
|
144
129
|
hello_help: I18nKey = I18nKey(
|
|
145
130
|
key=\"module.{name}.command.hello.help\",
|
|
146
131
|
default=\"Send a greeting message\",
|
|
@@ -60,7 +60,7 @@ RUN_PERSISTENT_CRASH_HINT_THRESHOLD: int = 3
|
|
|
60
60
|
# append_pyproject_dependencies 四处。
|
|
61
61
|
# 修改影响: 真实 pip 依赖——pip 默认不解析预发布版本,该下限不得高于已发布的
|
|
62
62
|
# 正式版本(与 create.py 的 _TEMPLATE_MIN_SDK_VERSION 同步升版)。
|
|
63
|
-
INIT_ERISPULSE_DEPENDENCY: str = "erispulse>=2.
|
|
63
|
+
INIT_ERISPULSE_DEPENDENCY: str = "erispulse>=2.10.0"
|
|
64
64
|
|
|
65
65
|
__all__ = [
|
|
66
66
|
"ADAPTER_ENTRY_POINT_GROUP",
|
|
@@ -55,8 +55,17 @@ class BaseConverter:
|
|
|
55
55
|
|
|
56
56
|
@staticmethod
|
|
57
57
|
def at(user_id: str) -> dict:
|
|
58
|
-
"""
|
|
59
|
-
|
|
58
|
+
"""
|
|
59
|
+
构造 @ 消息段
|
|
60
|
+
|
|
61
|
+
方法名保留 ``at`` 别名,产出为 OB12 规范的 ``mention`` 段——
|
|
62
|
+
此前产出 ``{"type": "at"}``(OB11 遗留形态),框架全部 mention
|
|
63
|
+
判定(must_at_bot / has_mention / on_at_message)均不识别该形态。
|
|
64
|
+
"""
|
|
65
|
+
return {"type": "mention", "data": {"user_id": user_id}}
|
|
66
|
+
|
|
67
|
+
# mention 为规范名别名(2.10.1 起推荐显式使用)
|
|
68
|
+
mention = at
|
|
60
69
|
|
|
61
70
|
@staticmethod
|
|
62
71
|
def image(file: str) -> dict:
|
|
@@ -386,9 +386,12 @@ class BaseEventHandler:
|
|
|
386
386
|
},
|
|
387
387
|
)
|
|
388
388
|
|
|
389
|
-
#
|
|
389
|
+
# 忽略自己发送的消息(self 标识取 account_id or user_id,与全框架口径一致:
|
|
390
|
+
# 仅填 self.account_id 的适配器此前会漏判导致机器人消息自激分发)
|
|
390
391
|
if self.event_type == EVENT_TYPE_MESSAGE:
|
|
391
|
-
|
|
392
|
+
_self_info = event.get("self", {})
|
|
393
|
+
_self_id = _self_info.get("account_id") or _self_info.get("user_id")
|
|
394
|
+
if _self_id is not None and str(_self_id) == str(event.get("user_id")):
|
|
392
395
|
event_config = get_event_config()
|
|
393
396
|
ignore_self = event_config.get("message", {}).get("ignore_self", True)
|
|
394
397
|
if ignore_self:
|
|
@@ -419,11 +422,32 @@ class BaseEventHandler:
|
|
|
419
422
|
for _priority, group_iter in groupby(self.handlers, key=lambda h: h["priority"]):
|
|
420
423
|
group = list(group_iter)
|
|
421
424
|
|
|
422
|
-
# 过滤出满足条件的处理器(条件函数 + 作用域模块维度 +
|
|
425
|
+
# 过滤出满足条件的处理器(条件函数 + 作用域模块维度 + 事件作用域文本过滤)。
|
|
426
|
+
# 条件函数逐个异常隔离:一个坏模块的条件崩溃只影响自身命中与否,
|
|
427
|
+
# 不得拖死同优先级组的其它处理器(含命令分发器)
|
|
428
|
+
def _condition_ok(h: dict) -> bool:
|
|
429
|
+
condition = h.get("condition")
|
|
430
|
+
if not condition:
|
|
431
|
+
return True
|
|
432
|
+
try:
|
|
433
|
+
return bool(condition(event))
|
|
434
|
+
except Exception as _cond_err:
|
|
435
|
+
_h_qualname = getattr(
|
|
436
|
+
h["func"], "__qualname__", getattr(h["func"], "__name__", str(h["func"]))
|
|
437
|
+
)
|
|
438
|
+
logger.error(
|
|
439
|
+
i18n.t(
|
|
440
|
+
"core.event.condition_error",
|
|
441
|
+
handler=_h_qualname,
|
|
442
|
+
error=_cond_err,
|
|
443
|
+
)
|
|
444
|
+
)
|
|
445
|
+
return False
|
|
446
|
+
|
|
423
447
|
active = [
|
|
424
448
|
h
|
|
425
449
|
for h in group
|
|
426
|
-
if (
|
|
450
|
+
if _condition_ok(h)
|
|
427
451
|
and self._is_scope_allowed(h, scope_platform, scope_bot, scope_session)
|
|
428
452
|
and self._is_scope_handler_ok(h, event)
|
|
429
453
|
]
|
|
@@ -391,6 +391,10 @@ class CommandHandler:
|
|
|
391
391
|
# 使用函数名作为命令名
|
|
392
392
|
cmd_names = [func.__name__]
|
|
393
393
|
|
|
394
|
+
# 注册期校验(fail-fast):空列表 / 非 str 名此前会 IndexError 或静默产出垃圾名
|
|
395
|
+
if not cmd_names or any(not isinstance(n, str) or not n for n in cmd_names):
|
|
396
|
+
raise ValueError(i18n.t("core.command.invalid_name", func=func.__qualname__))
|
|
397
|
+
|
|
394
398
|
main_name = cmd_names[0]
|
|
395
399
|
|
|
396
400
|
# 声明式参数/选项(args= / options=)注册期解析与校验(fail-fast):
|
|
@@ -861,6 +865,16 @@ class CommandHandler:
|
|
|
861
865
|
# (命中 / 取消路径条目已被移除,此处仅兜底超时路径的残留)
|
|
862
866
|
interaction.cancel(wait_key)
|
|
863
867
|
|
|
868
|
+
# 文本处理三态结果(_process_text_for_command / _try_execute_command 的返回契约):
|
|
869
|
+
# unmatched = 文本未解析出任何注册命令(允许 alt_message 兜底重试);
|
|
870
|
+
# consumed = 命令已执行 / 治理拦截 / 参数错误等已消费路径;
|
|
871
|
+
# rejected = 命中注册命令但被 scope/ACL/master/permission 拒绝。
|
|
872
|
+
# 命中注册命令后(无论拒绝与否)不再用 alt_message 重跑整条链——
|
|
873
|
+
# 否则拒绝回复会重复发送、用户自定义权限函数副作用会双触发。
|
|
874
|
+
_STATE_UNMATCHED = "unmatched"
|
|
875
|
+
_STATE_CONSUMED = "consumed"
|
|
876
|
+
_STATE_REJECTED = "rejected"
|
|
877
|
+
|
|
864
878
|
async def _handle_message(self, event: dict[str, Any]):
|
|
865
879
|
"""
|
|
866
880
|
处理消息事件中的命令
|
|
@@ -914,7 +928,7 @@ class CommandHandler:
|
|
|
914
928
|
)
|
|
915
929
|
return
|
|
916
930
|
|
|
917
|
-
async def _process_text_for_command(event: "Event", text: str) ->
|
|
931
|
+
async def _process_text_for_command(event: "Event", text: str) -> str:
|
|
918
932
|
"""
|
|
919
933
|
处理文本内容,尝试匹配并执行命令
|
|
920
934
|
|
|
@@ -923,18 +937,25 @@ class CommandHandler:
|
|
|
923
937
|
|
|
924
938
|
:param event: 消息事件数据
|
|
925
939
|
:param text: 要处理的文本内容
|
|
926
|
-
:return:
|
|
940
|
+
:return: 处理状态三态(unmatched / consumed / rejected,
|
|
941
|
+
见 :attr:`_STATE_UNMATCHED` 系列常量)
|
|
942
|
+
{!--< /internal-use >!--}
|
|
927
943
|
"""
|
|
928
944
|
if not text:
|
|
929
|
-
return
|
|
945
|
+
return self._STATE_UNMATCHED
|
|
946
|
+
|
|
947
|
+
# 前导空白容忍:"@bot /kick x" 场景下首段文本常以空格开头
|
|
948
|
+
# (mention 段后的空格随平台输入分段进入 text),不剥离则前缀永不命中
|
|
949
|
+
text = text.lstrip()
|
|
930
950
|
|
|
931
951
|
# 处理大小写敏感性
|
|
932
952
|
check_text = text if self.case_sensitive else text.lower()
|
|
933
953
|
prefixes = self._prefixes if self.case_sensitive else [p.lower() for p in self._prefixes]
|
|
934
954
|
|
|
935
|
-
#
|
|
955
|
+
# 检查前缀,找出匹配的前缀(支持多个前缀,最长优先——
|
|
956
|
+
# 避免 "/a" 抢占 "/ab" 导致后者整组命令不可达)
|
|
936
957
|
matched_prefix = None
|
|
937
|
-
for prefix in prefixes:
|
|
958
|
+
for prefix in sorted(prefixes, key=len, reverse=True):
|
|
938
959
|
has_prefix = check_text.startswith(prefix)
|
|
939
960
|
has_space_prefix = self.allow_space_prefix and check_text.startswith(prefix + " ")
|
|
940
961
|
if has_prefix or has_space_prefix:
|
|
@@ -949,21 +970,32 @@ class CommandHandler:
|
|
|
949
970
|
user_id=event.get("user_id", ""),
|
|
950
971
|
)
|
|
951
972
|
)
|
|
952
|
-
return
|
|
973
|
+
return self._STATE_UNMATCHED
|
|
953
974
|
|
|
954
975
|
# 检查是否必须@机器人
|
|
955
976
|
if self.must_at_bot:
|
|
956
977
|
detail_type = infer_receive_type(event)
|
|
957
978
|
# 一对一场景(private或user)不需要检查@
|
|
958
979
|
if detail_type not in (DETAIL_TYPE_PRIVATE, DETAIL_TYPE_USER):
|
|
959
|
-
message_segments = event.get("message"
|
|
960
|
-
self_id = event.get("self", {}).get("user_id")
|
|
980
|
+
message_segments = event.get("message") or []
|
|
981
|
+
self_id = event.get_self_account_id() or event.get("self", {}).get("user_id")
|
|
961
982
|
|
|
962
983
|
has_mention = False
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
984
|
+
if isinstance(message_segments, list):
|
|
985
|
+
for segment in message_segments:
|
|
986
|
+
if not isinstance(segment, dict):
|
|
987
|
+
continue
|
|
988
|
+
data = segment.get("data")
|
|
989
|
+
segment_user_id = data.get("user_id") if isinstance(data, dict) else None
|
|
990
|
+
# user_id 两侧 str() 归一:适配器透传平台原生 int 时不失配
|
|
991
|
+
if (
|
|
992
|
+
segment.get("type") == "mention"
|
|
993
|
+
and segment_user_id is not None
|
|
994
|
+
and self_id is not None
|
|
995
|
+
and str(segment_user_id) == str(self_id)
|
|
996
|
+
):
|
|
997
|
+
has_mention = True
|
|
998
|
+
break
|
|
967
999
|
|
|
968
1000
|
if not has_mention:
|
|
969
1001
|
logger.trace(
|
|
@@ -973,37 +1005,52 @@ class CommandHandler:
|
|
|
973
1005
|
user_id=event.get("user_id", ""),
|
|
974
1006
|
)
|
|
975
1007
|
)
|
|
976
|
-
|
|
1008
|
+
# 文本是命令形态但未@机器人:判定为拒绝(alt_message
|
|
1009
|
+
# 走同样的段判定,重试不可能改变结果)
|
|
1010
|
+
return self._STATE_REJECTED
|
|
977
1011
|
|
|
978
1012
|
# 尝试执行命令
|
|
979
1013
|
return await self._try_execute_command(event, text, matched_prefix)
|
|
980
1014
|
|
|
981
|
-
# 从 message
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1015
|
+
# 从 message 段列表提取命令文本:按段顺序收集全部 text 段并以空格拼接——
|
|
1016
|
+
# 平台常把一条消息切成多个 text 段(如 mention 前后各一段),只取首段会
|
|
1017
|
+
# 静默丢失尾部参数。畸形输入全程容错(None / 字符串 / 非 dict 段 / data 非 dict)
|
|
1018
|
+
message_segments = event.get("message") or []
|
|
1019
|
+
raw_text_parts: list[str] = []
|
|
1020
|
+
if isinstance(message_segments, str):
|
|
1021
|
+
# OB11 遗留形态:适配器直接塞 CQ 码 / 纯文本字符串,按单段文本处理
|
|
1022
|
+
if message_segments:
|
|
1023
|
+
raw_text_parts.append(message_segments)
|
|
1024
|
+
elif isinstance(message_segments, list):
|
|
1025
|
+
for segment in message_segments:
|
|
1026
|
+
if not isinstance(segment, dict):
|
|
1027
|
+
continue
|
|
1028
|
+
if segment.get("type") != "text":
|
|
1029
|
+
continue
|
|
1030
|
+
segment_data = segment.get("data")
|
|
1031
|
+
segment_text = segment_data.get("text", "") if isinstance(segment_data, dict) else ""
|
|
1032
|
+
if segment_text:
|
|
1033
|
+
raw_text_parts.append(segment_text)
|
|
1034
|
+
message_text = " ".join(raw_text_parts)
|
|
988
1035
|
|
|
989
1036
|
alt_message = event.get("alt_message", "")
|
|
990
1037
|
|
|
991
|
-
#
|
|
1038
|
+
# 三态流转:仅当文本未命中任何注册命令时才用 alt_message 兜底——
|
|
1039
|
+
# 命中即止(无论执行 / 治理拦截 / 权限拒绝),杜绝拒绝链重复执行
|
|
1040
|
+
command_state = self._STATE_UNMATCHED
|
|
992
1041
|
if message_text:
|
|
993
|
-
|
|
994
|
-
if command_matched:
|
|
995
|
-
return
|
|
1042
|
+
command_state = await _process_text_for_command(event, message_text)
|
|
996
1043
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1044
|
+
if command_state == self._STATE_UNMATCHED and alt_message and alt_message != message_text:
|
|
1045
|
+
command_state = await _process_text_for_command(event, alt_message)
|
|
1046
|
+
|
|
1047
|
+
if command_state != self._STATE_UNMATCHED:
|
|
1048
|
+
return
|
|
1002
1049
|
|
|
1003
|
-
#
|
|
1050
|
+
# 决策链:带前缀的文本未命中任何注册命令(或非命令文本)——放行给消息处理器。
|
|
1051
|
+
# 交互等待已在方法开头 resolve 过(单次判定),此处不再重复调用:
|
|
1052
|
+
# 命令处理器内新注册的 wait_reply 不应被本条触发消息消费
|
|
1004
1053
|
trace_step("dispatch", "passed", "core.trace.passed_to_message")
|
|
1005
|
-
# 如果都没有匹配,检查是否是等待回复的消息
|
|
1006
|
-
await self._check_pending_reply(event)
|
|
1007
1054
|
return
|
|
1008
1055
|
|
|
1009
1056
|
def _is_command_text(self, text: str) -> bool:
|
|
@@ -1024,11 +1071,13 @@ class CommandHandler:
|
|
|
1024
1071
|
if not text:
|
|
1025
1072
|
return False
|
|
1026
1073
|
|
|
1074
|
+
# 前导空白容忍与最长前缀优先:口径与 _process_text_for_command 保持一致
|
|
1075
|
+
text = text.lstrip()
|
|
1027
1076
|
check_text = text if self.case_sensitive else text.lower()
|
|
1028
1077
|
prefixes = self._prefixes if self.case_sensitive else [p.lower() for p in self._prefixes]
|
|
1029
1078
|
|
|
1030
1079
|
matched_prefix = None
|
|
1031
|
-
for prefix in prefixes:
|
|
1080
|
+
for prefix in sorted(prefixes, key=len, reverse=True):
|
|
1032
1081
|
has_prefix = check_text.startswith(prefix)
|
|
1033
1082
|
has_space_prefix = self.allow_space_prefix and check_text.startswith(prefix + " ")
|
|
1034
1083
|
if has_prefix or has_space_prefix:
|
|
@@ -1046,7 +1095,7 @@ class CommandHandler:
|
|
|
1046
1095
|
cmd_name, _actual, _matched = self._resolve_command_tokens(parts)
|
|
1047
1096
|
return cmd_name is not None
|
|
1048
1097
|
|
|
1049
|
-
async def _try_execute_command(self, event: "Event", original_text: str, prefix: str) ->
|
|
1098
|
+
async def _try_execute_command(self, event: "Event", original_text: str, prefix: str) -> str:
|
|
1050
1099
|
"""
|
|
1051
1100
|
尝试执行命令
|
|
1052
1101
|
|
|
@@ -1056,14 +1105,15 @@ class CommandHandler:
|
|
|
1056
1105
|
:param event: 消息事件数据
|
|
1057
1106
|
:param original_text: 原始文本内容(命令名匹配时按配置做大小写归一,参数保留原文)
|
|
1058
1107
|
:param prefix: 已匹配的命令前缀(可能已转换为小写)
|
|
1059
|
-
:return:
|
|
1108
|
+
:return: 处理状态三态(unmatched / consumed / rejected,
|
|
1109
|
+
见 :attr:`_STATE_UNMATCHED` 系列常量)
|
|
1060
1110
|
"""
|
|
1061
1111
|
# 解析命令和参数:参数与 raw 载荷保留用户原始大小写(取自 original_text,
|
|
1062
1112
|
# 前缀为配置字面量、在大小写归一前后长度一致),仅命令名匹配候选做归一
|
|
1063
1113
|
command_text = original_text[len(prefix) :].strip()
|
|
1064
1114
|
raw_parts = command_text.split()
|
|
1065
1115
|
if not raw_parts:
|
|
1066
|
-
return
|
|
1116
|
+
return self._STATE_UNMATCHED
|
|
1067
1117
|
|
|
1068
1118
|
# 处理大小写敏感性:候选名归一化用于匹配,参数列表保留原始形式
|
|
1069
1119
|
parts = raw_parts if self.case_sensitive else [part.lower() for part in raw_parts]
|
|
@@ -1087,7 +1137,7 @@ class CommandHandler:
|
|
|
1087
1137
|
text=f"{prefix}{raw_parts[0]}",
|
|
1088
1138
|
suggestion=suggestion or "",
|
|
1089
1139
|
)
|
|
1090
|
-
return
|
|
1140
|
+
return self._STATE_UNMATCHED
|
|
1091
1141
|
args = raw_parts[matched:]
|
|
1092
1142
|
|
|
1093
1143
|
# 命中即认领:文本已解析为一条注册命令,无论后续作用域 / ACL /
|
|
@@ -1127,7 +1177,8 @@ class CommandHandler:
|
|
|
1127
1177
|
cmd_info = self.commands[actual_cmd_name]
|
|
1128
1178
|
handler = cmd_info["func"]
|
|
1129
1179
|
|
|
1130
|
-
# 作用域检查:模块未对该 Bot / 会话 /
|
|
1180
|
+
# 作用域检查:模块未对该 Bot / 会话 / 平台启用时静默不回复
|
|
1181
|
+
# (事件已在命令命中时统一认领,对低优先级处理器不可见)
|
|
1131
1182
|
cmd_owner = cmd_info.get("owner")
|
|
1132
1183
|
if cmd_owner:
|
|
1133
1184
|
from ..scope import scope
|
|
@@ -1146,7 +1197,7 @@ class CommandHandler:
|
|
|
1146
1197
|
command=actual_cmd_name,
|
|
1147
1198
|
module=cmd_owner,
|
|
1148
1199
|
)
|
|
1149
|
-
return
|
|
1200
|
+
return self._STATE_REJECTED
|
|
1150
1201
|
|
|
1151
1202
|
# 命令用户 ACL(event.overrides.acl):命令名支持 glob
|
|
1152
1203
|
# deny 命中 / allow 白名单未命中 / 严格模式无 ACL → 拒绝;
|
|
@@ -1172,7 +1223,7 @@ class CommandHandler:
|
|
|
1172
1223
|
)
|
|
1173
1224
|
)
|
|
1174
1225
|
await self._send_permission_denied(event)
|
|
1175
|
-
return
|
|
1226
|
+
return self._STATE_REJECTED
|
|
1176
1227
|
|
|
1177
1228
|
# 命令实现参数覆写(event.overrides.command):覆盖 master / hidden / aliases / prefix 等
|
|
1178
1229
|
# 覆写键 master 由 overrides.command.apply 统一映射到存储键 must_master(用户优先)
|
|
@@ -1201,7 +1252,7 @@ class CommandHandler:
|
|
|
1201
1252
|
)
|
|
1202
1253
|
)
|
|
1203
1254
|
await self._send_permission_denied(event)
|
|
1204
|
-
return
|
|
1255
|
+
return self._STATE_REJECTED
|
|
1205
1256
|
|
|
1206
1257
|
# 检查权限:子命令未声明权限时继承最近声明了权限的祖先命令
|
|
1207
1258
|
# (如 /admin 声明权限后,/admin add 等子命令自动受同一权限保护)
|
|
@@ -1233,7 +1284,7 @@ class CommandHandler:
|
|
|
1233
1284
|
command=actual_cmd_name,
|
|
1234
1285
|
)
|
|
1235
1286
|
await self._send_permission_denied(event)
|
|
1236
|
-
return
|
|
1287
|
+
return self._STATE_REJECTED
|
|
1237
1288
|
except Exception as e:
|
|
1238
1289
|
logger.error(i18n.t("core.command.permission_check_error", error=e))
|
|
1239
1290
|
trace_step(
|
|
@@ -1244,7 +1295,7 @@ class CommandHandler:
|
|
|
1244
1295
|
error=str(e),
|
|
1245
1296
|
)
|
|
1246
1297
|
await self._send_permission_denied(event)
|
|
1247
|
-
return
|
|
1298
|
+
return self._STATE_REJECTED
|
|
1248
1299
|
|
|
1249
1300
|
# 添加命令相关信息到事件(合并覆写后的有效参数)
|
|
1250
1301
|
# owner:注册该命令的模块(懒激活占位命令即目标模块),
|
|
@@ -1261,6 +1312,9 @@ class CommandHandler:
|
|
|
1261
1312
|
"owner": cmd_info.get("owner"),
|
|
1262
1313
|
}
|
|
1263
1314
|
|
|
1315
|
+
if "command" in event:
|
|
1316
|
+
# 平台 payload 透传了同名键:框架写入会覆盖原始数据,告警留痕
|
|
1317
|
+
logger.warning(i18n.t("core.command.key_collision", platform=event.get("platform", UNKNOWN_PLATFORM)))
|
|
1264
1318
|
event["command"] = command_info
|
|
1265
1319
|
|
|
1266
1320
|
# 认领已在命令命中时完成(见上方 mark_processed),
|
|
@@ -1325,7 +1379,7 @@ class CommandHandler:
|
|
|
1325
1379
|
"error": error_text,
|
|
1326
1380
|
},
|
|
1327
1381
|
)
|
|
1328
|
-
return
|
|
1382
|
+
return self._STATE_CONSUMED
|
|
1329
1383
|
|
|
1330
1384
|
# 治理判定(cooldown / rate_limit / usage,EPRFC-2026-001 方向七):
|
|
1331
1385
|
# 位于全部权限检查与参数解析之后——无权限用户不触发计时,
|
|
@@ -1335,15 +1389,15 @@ class CommandHandler:
|
|
|
1335
1389
|
if await self._gate.check_cooldown(
|
|
1336
1390
|
cmd_info["main_name"], actual_cmd_name, _effective, event, self._send_args_error
|
|
1337
1391
|
):
|
|
1338
|
-
return
|
|
1392
|
+
return self._STATE_CONSUMED
|
|
1339
1393
|
if await self._gate.check_rate_limit(
|
|
1340
1394
|
cmd_info["main_name"], actual_cmd_name, _effective, event, self._send_args_error
|
|
1341
1395
|
):
|
|
1342
|
-
return
|
|
1396
|
+
return self._STATE_CONSUMED
|
|
1343
1397
|
if await self._gate.check_usage(
|
|
1344
1398
|
cmd_info["main_name"], actual_cmd_name, _effective, event, self._send_args_error
|
|
1345
1399
|
):
|
|
1346
|
-
return
|
|
1400
|
+
return self._STATE_CONSUMED
|
|
1347
1401
|
|
|
1348
1402
|
# 废弃声明(deprecated=):调用时自动回复废弃文案;默认继续执行,
|
|
1349
1403
|
# deprecated_reject=True 时拒绝执行(命令已认领,不漏给消息处理器)
|
|
@@ -1379,7 +1433,7 @@ class CommandHandler:
|
|
|
1379
1433
|
"error": "deprecated",
|
|
1380
1434
|
},
|
|
1381
1435
|
)
|
|
1382
|
-
return
|
|
1436
|
+
return self._STATE_CONSUMED
|
|
1383
1437
|
trace_step(
|
|
1384
1438
|
"deprecated",
|
|
1385
1439
|
"notice",
|
|
@@ -1455,7 +1509,7 @@ class CommandHandler:
|
|
|
1455
1509
|
},
|
|
1456
1510
|
)
|
|
1457
1511
|
|
|
1458
|
-
return
|
|
1512
|
+
return self._STATE_CONSUMED
|
|
1459
1513
|
|
|
1460
1514
|
logger.trace(
|
|
1461
1515
|
i18n.t(
|
|
@@ -1465,7 +1519,7 @@ class CommandHandler:
|
|
|
1465
1519
|
user_id=event.get("user_id", ""),
|
|
1466
1520
|
)
|
|
1467
1521
|
)
|
|
1468
|
-
return
|
|
1522
|
+
return self._STATE_UNMATCHED
|
|
1469
1523
|
|
|
1470
1524
|
async def _check_pending_reply(self, event: "Event"):
|
|
1471
1525
|
"""
|
|
@@ -83,6 +83,7 @@ class _Entry:
|
|
|
83
83
|
"future",
|
|
84
84
|
"key",
|
|
85
85
|
"kind",
|
|
86
|
+
"matcher",
|
|
86
87
|
"owner",
|
|
87
88
|
"pattern",
|
|
88
89
|
"platform",
|
|
@@ -103,6 +104,8 @@ class _Entry:
|
|
|
103
104
|
self.validator: Any = None
|
|
104
105
|
self.pattern: str | None = None
|
|
105
106
|
self.regex: str | None = None
|
|
107
|
+
# 自定义匹配器(select 多路等待聚合用):提供时完全取代 pattern/regex/validator 链
|
|
108
|
+
self.matcher: Any = None
|
|
106
109
|
# 命令穿透三态(2.8.3):None=跟随全局配置 / True=强制豁免 / False=强制吞
|
|
107
110
|
self.cmdpass: bool | None = None
|
|
108
111
|
# lease 类别字段
|
|
@@ -290,7 +293,10 @@ class InteractionManager:
|
|
|
290
293
|
_send_type, target_id = get_send_type_and_target_id(event, platform)
|
|
291
294
|
self_info = event.get("self") or {}
|
|
292
295
|
bot_id = self_info.get("account_id", "") or self_info.get("user_id", "")
|
|
293
|
-
|
|
296
|
+
# 并入 detail_type:同平台同 bot 下 user_id 与 group_id 数值碰撞时
|
|
297
|
+
# (如 user 123 与 group 123),私聊与群聊的会话级等待不再互相命中
|
|
298
|
+
detail_type = event.get("detail_type") or ""
|
|
299
|
+
return f"{platform}:{bot_id}:{detail_type}:{target_id}"
|
|
294
300
|
|
|
295
301
|
# ==================== 索引维护 ====================
|
|
296
302
|
|
|
@@ -342,6 +348,7 @@ class InteractionManager:
|
|
|
342
348
|
owner: str | None = None,
|
|
343
349
|
session_scope: bool = False,
|
|
344
350
|
cmdpass: bool | None = None,
|
|
351
|
+
matcher: Any = None,
|
|
345
352
|
) -> _Entry:
|
|
346
353
|
"""
|
|
347
354
|
{!--< internal-use >!--}
|
|
@@ -361,6 +368,8 @@ class InteractionManager:
|
|
|
361
368
|
:param session_scope: 会话级等待(同会话任何人的回复均可命中,键不含 user 维度)
|
|
362
369
|
:param cmdpass: 是否跳过命令匹配的三态(None=跟随全局配置,默认不跳过;
|
|
363
370
|
True=跳过命令匹配,等待期间消息一律作为回复消费)
|
|
371
|
+
:param matcher: 自定义命中判定函数 ``callable(Event) -> bool``,提供时完全取代
|
|
372
|
+
pattern/regex/validator 链(select 多路等待的聚合入口;异常按不命中处理)
|
|
364
373
|
:return: 注册的条目(含推导的会话键)
|
|
365
374
|
"""
|
|
366
375
|
if session_scope:
|
|
@@ -383,6 +392,7 @@ class InteractionManager:
|
|
|
383
392
|
entry.pattern = pattern
|
|
384
393
|
entry.regex = regex
|
|
385
394
|
entry.cmdpass = cmdpass
|
|
395
|
+
entry.matcher = matcher
|
|
386
396
|
entry.timestamp = time.monotonic()
|
|
387
397
|
|
|
388
398
|
if session_scope:
|
|
@@ -430,9 +440,13 @@ class InteractionManager:
|
|
|
430
440
|
.get("cmdpass", DEFAULT_WAIT_REPLY_CMDPASS)
|
|
431
441
|
)
|
|
432
442
|
if not cmdpass and event.get("type", "") == "message":
|
|
443
|
+
# 文本口径与命令分发一致(alt_message 优先 + 段回退):
|
|
444
|
+
# 仅填 message 段的事件此前判空直接放行,命令穿透永不生效。
|
|
445
|
+
# 经 extract_text 提取:resolve 的入参可能是裸 dict(非 Event 包装)
|
|
446
|
+
from ..text_match import extract_text
|
|
433
447
|
from .command import command as _command_handler
|
|
434
448
|
|
|
435
|
-
text = event
|
|
449
|
+
text = extract_text(event)
|
|
436
450
|
if text and _command_handler._is_command_text(text):
|
|
437
451
|
logger.trace(
|
|
438
452
|
i18n.t(
|
|
@@ -444,10 +458,16 @@ class InteractionManager:
|
|
|
444
458
|
)
|
|
445
459
|
return False
|
|
446
460
|
|
|
447
|
-
#
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
461
|
+
# 命中判定:matcher(自定义聚合判定,select 多路等待用)优先,
|
|
462
|
+
# 否则走 pattern(glob)/ regex(正则)过滤 + validator 校验链——
|
|
463
|
+
# 不匹配则继续等待(不消费 future)
|
|
464
|
+
if entry.matcher is not None:
|
|
465
|
+
try:
|
|
466
|
+
matched = bool(entry.matcher(event))
|
|
467
|
+
except Exception as e:
|
|
468
|
+
logger.error(i18n.t("core.interaction.matcher_error", error=e))
|
|
469
|
+
matched = False
|
|
470
|
+
if not matched:
|
|
451
471
|
logger.trace(
|
|
452
472
|
i18n.t(
|
|
453
473
|
"core.interaction.reply_not_matched",
|
|
@@ -457,23 +477,35 @@ class InteractionManager:
|
|
|
457
477
|
)
|
|
458
478
|
)
|
|
459
479
|
return False
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
try:
|
|
465
|
-
if not validator(event):
|
|
480
|
+
else:
|
|
481
|
+
if entry.pattern or entry.regex:
|
|
482
|
+
text_cond = compile_text_matcher(entry.pattern, entry.regex)
|
|
483
|
+
if text_cond is not None and not text_cond(event):
|
|
466
484
|
logger.trace(
|
|
467
485
|
i18n.t(
|
|
468
|
-
"core.interaction.
|
|
486
|
+
"core.interaction.reply_not_matched",
|
|
469
487
|
wait_key=key,
|
|
470
488
|
user_id=event.get("user_id", ""),
|
|
489
|
+
platform=event.get("platform", UNKNOWN_PLATFORM),
|
|
471
490
|
)
|
|
472
491
|
)
|
|
473
492
|
return False
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
493
|
+
|
|
494
|
+
validator = entry.validator
|
|
495
|
+
if validator is not None:
|
|
496
|
+
try:
|
|
497
|
+
if not validator(event):
|
|
498
|
+
logger.trace(
|
|
499
|
+
i18n.t(
|
|
500
|
+
"core.interaction.reply_validation_failed",
|
|
501
|
+
wait_key=key,
|
|
502
|
+
user_id=event.get("user_id", ""),
|
|
503
|
+
)
|
|
504
|
+
)
|
|
505
|
+
return False
|
|
506
|
+
except Exception as e:
|
|
507
|
+
logger.error(i18n.t("core.interaction.validator_error", error=e))
|
|
508
|
+
return False
|
|
477
509
|
|
|
478
510
|
# 权限复查:会话建立后被拉黑 / owner 模块被解绑 → 终止等待
|
|
479
511
|
revoked_reason = self._check_revoked(entry, event)
|