简体中文 English 繁體中文 日本語 Русский
本文为静态镜像,内容以交互版为准 在交互式文档中心打开 →

作用域(scope)

Note

本特性需要 ErisPulse **2.8.0+**。

作用域回答四个问题:哪些模块可用、谁的事件收不收、某模块处理什么文本、 模块能向外做什么。 控制权完全交给用户:在模块 / 适配器 / 处理器 / 出站调用注册的上层(配置 ErisPulse.scope 或运行时 sdk.scope)统一声明,事件管线在入口、处理器过滤 与出站闸口自动读取并执行。

维度 控制什么 拒绝行为 配置路径
① 模块 哪些模块可用(平台 / Bot / 会话三级) 被过滤模块不触发、不回复(拦截广播 scope.blocked 事件;命中的命令仍被认领阻断) scope.platforms / bots / sessions
② 身份 事件收不收(适配器 / Bot / 会话 / 用户四级) 入口完全丢弃(拦截广播 scope.blocked 事件) scope.identity.*
③ 出站 模块能发起哪些出站调用(消息 / API / 请求,方法级白黑名单) 失败响应(retcode=34601) scope.actions

相关系统:命令是特殊的消息事件处理器,其用户黑白名单(ACL)与 实现参数覆写由命令系统自持(ErisPulse.event.command), 见 事件处理入门 与 配置指南。

{!--< tips >!--}

  1. 通过 from ErisPulse.Core import scope 导入单例(sdk.scope 同对象)
  2. 判定:scope.is_allowed(...) / scope.is_identity_allowed(...) / scope.is_action_allowed(...) 对应 ①②③ 三个闸口
  3. 读写:维度化参数方法(IDE 可补全)—— scope.set_module(...) / scope.set_identity(...) / scope.set_action(...); 另有字典式兜底 scope.get(path) / scope.set(path, v) / scope.delete(path)
  4. 事件处理器文本条件覆写见 事件处理入门 · 事件覆写; 命令 ACL / 参数覆写见事件处理入门 {!--< /tips >!--}

匹配条目语法(全系统统一)

作用域所有"名字列表"(模块名、身份键、出站条目)共用同一套匹配语法 (ErisPulse.Core.text_match):

语法 示例 说明
精确名 "Chat" 全值比较,大小写不敏感
glob "Tool*"、"spam_*" * 任意串 / ? 单字符 / [seq] 字符集,大小写不敏感
正则 "re:^Danger.*" 以 re: 前缀声明,正则 search 匹配,默认大小写不敏感

全局兜底:default_allow

default_allow 是全局唯一的兜底开关(默认 true), 对两个判定维度统一生效:

设为 false 即开启"隐式拒绝"严格模式:白名单式管理, 没显式允许的一律拒绝。

例外:③ 出站维度不受 default_allow 影响——它是独立的收紧开关, 默认全允许,仅显式规则才限制(框架层 owner 为空的调用恒放行)。 这样严格的全局模式不会意外掐断所有模块的消息回复。 命令 ACL 有独立的 ErisPulse.event.command.default_allow 兜底,互不影响。

配置文件

[ErisPulse.scope]
default_allow = true        # 全局兜底(false = 隐式拒绝严格模式)
cache_size = 1024           # LRU 缓存大小

# ── ① 模块维度(优先级:会话 > Bot > 平台)──
[ErisPulse.scope.platforms.onebot11]
modules = ["Chat", "Tool*"]   # 白名单:精确名 / glob / re: 正则
blocked = ["re:^Danger"]
[ErisPulse.scope.bots.onebot11."123456"]
modules = ["Chat"]
merge = true                  # 在平台级绑定基础上追加(默认整体覆盖)
[ErisPulse.scope.sessions.onebot11."789012345"]
modules = ["Chat"]

# ── ② 身份维度(优先级:用户 > 会话 > Bot > 适配器)──
[ErisPulse.scope.identity.adapters.onebot11]
deny = true                   # 整个适配器的事件全部丢弃
[ErisPulse.scope.identity.bots.onebot11."123456"]
deny = true
[ErisPulse.scope.identity.sessions.onebot11."g_blocked"]
deny = true
[ErisPulse.scope.identity.users.onebot11]
allow = ["u_admin"]           # 用户键支持 glob / re: 正则
deny = ["u_bad", "spam_*"]

# ── ③ 出站维度(默认全允许,显式收紧才禁)──
[ErisPulse.scope.actions.MyModule]
send = { deny = true }                                    # 全禁发送
api = { allow = ["get_*"] }                               # 仅允许查询类标准 API
request = { deny = true }                                 # 禁止处理请求

① 模块维度

回答"某个上下文里,哪些模块可用"。默认全部开放;配置绑定后才开始过滤, 模块与适配器无需任何改动。

flowchart TD
    A["事件到达某模块的处理器/命令"] --> B{"scope.is_allowed<br/>(platform, bot, module, session)"}
    B --> C{"解析链:会话级 > Bot 级 > 平台级<br/>(子级 merge = true 时逐级并集)"}
    C -->|"命中"| D["blocked 命中 → 拒绝<br/>modules 非空 → 仅白名单放行<br/>都空 → default_allow"]
    C -->|"未命中"| E["default_allow(默认 true = 放行)"]
    D -->|"拒绝"| Z["不回复<br/>(拦截广播 `scope.blocked` 事件;命中的命令仍被认领阻断)"]

绑定继承(merge)

默认整体覆盖的语义清晰可预测;需要在上级基础上追加时,在子级写 merge = true:

[ErisPulse.scope.platforms.onebot11]
modules = ["Chat", "Tool"]      # 平台级:允许 Chat、Tool

[ErisPulse.scope.bots.onebot11."123456"]
modules = ["Music"]
merge = true                    # 该 Bot 实际生效 = ["Chat", "Tool", "Music"]

② 身份维度(事件准入)

回答"谁的事件收不收"。被拒绝的事件在分发入口完全丢弃—— 不进入中间件与任何处理器(含框架级),仅 TRACE 级日志可见(core.scope.identity_denied)。

[ErisPulse.scope.identity.adapters.onebot11]
deny = true
[ErisPulse.scope.identity.users.onebot11]
allow = ["u_admin"]   # 即使适配器级拒绝,u_admin 的事件仍然放行

③ 出站维度(限制模块发起出站调用)

约束模块发起的出站动作:消息发送 / 标准 API 动作 / 请求操作。 三类动作对应底层 DSL:Event.reply 与 Send(send)、Api / call_api(api)、 Request 的 accept/reject(request)。模块在事件 handler 执行期发起的出站调用 携带模块 owner,由本维度统一判定。

规则形态(内联表)

每个动作的规则是一张内联表:{ allow = [...], deny = true|[...] }。 同一动作只能有一种规则(TOML 键不可重复,全禁与细粒度二选一):

[ErisPulse.scope.actions.MyModule]
send = { deny = true }                                  # 全禁发送(Event.reply / Send DSL)
# 或方法级细粒度:send = { allow = ["Text", "Image*"], deny = ["File"] }
api = { allow = ["get_*"] }                             # 仅放行查询类标准 API
# 或动作级黑名单:api = { deny = ["set_*", "leave_*"] }
request = { deny = true }                               # 禁止处理请求 accept/reject

判定语义

默认全允许——未配置、或 owner 为空(框架层内部调用)均放行。 配置规则后按以下顺序判定:

  1. deny = true → 拒绝
  2. deny 列表命中调用名 → 拒绝
  3. allow 列表非空且调用名未命中(或调用无名称)→ 拒绝
  4. 其余放行

被拒调用不发起任何网络请求,直接返回标准失败响应 (retcode = 34601,见 api-response §5.3)。 三个动作互相独立,可只限其一。

# 运行时 API
sdk.scope.set_action("MyModule", "send", deny=True)              # 全禁发消息
sdk.scope.set_action("MyModule", "send", allow=["Text"])         # 仅允许发文本
sdk.scope.is_action_allowed("MyModule", "send", name="Image")    # False
sdk.scope.is_action_allowed("MyModule", "api", name="get_user_info")  # 按规则判定
sdk.scope.delete_action("MyModule", "send")                      # 恢复允许
sdk.scope.get_action("MyModule", "send")                         # 该动作当前规则

运行时 API

作用域运行时 API 分三层:判定(三问)、维度化读写(每维 set / get / delete 参数化方法,签名全类型标注,IDE 可补全)、字典式兜底(点分路径直达任意节)。

from ErisPulse import sdk

scope = sdk.scope

判定(三问)

scope.is_allowed("onebot11", "123456", "Chat")                 # ① 模块维度
scope.is_allowed("onebot11", "123456", "Chat", "789012345")    # 含会话级
scope.is_allowed("onebot11", "123456", None)                   # 框架层资源 -> True

scope.is_identity_allowed("onebot11", "123456", "group_9", "u1")   # ② 身份维度

scope.is_action_allowed("MyModule", "send")                    # ④ 出站维度
scope.is_action_allowed("MyModule", "send", name="Image")      # 方法级细粒度

① 模块维度

# 绑定(层级由参数决定:session_id > bot_id > 平台级)
scope.set_module("onebot11", bot_id="123456", modules=["Chat", "Tool*"])
scope.set_module("onebot11", blocked=["re:^Danger"])                       # 平台级
scope.set_module("onebot11", bot_id="123456", session_id="g9", modules=["Chat"])  # 会话级
scope.set_module("onebot11", bot_id="123456", modules=["Music"], merge=True)      # 与现有条目并集
scope.set_module("onebot11", bot_id="123456", modules=["Chat"], persist=False)    # 仅运行时

# 读 / 删
scope.get_module("onebot11", bot_id="123456")   # {"modules": ["Chat"], "blocked": []}
scope.delete_module("onebot11", bot_id="123456")

merge=True 是写时并集(与该级现有绑定合并条目);跨级解析期的 merge = true 配置键见上文绑定继承——两者是独立机制。

运行时绑定(persist=False)语义:运行时绑定保存在独立的覆盖层中, 任意后续配置写入 / 配置文件热更新都不会冲掉它们(配置树重建后按写入顺序 自动重放,含运行时删除)。它们不落盘,进程重启后丢失;模块卸载时该模块写入的 运行时绑定会被兜底清理。随后对同一路径执行 persist=True 写入(用户持久化语义) 将取代运行时规则。

② 身份维度

# 绑定策略(层级由参数决定:user > session > bot > adapter;allow / deny 二选一)
scope.set_identity("onebot11", user_id="u_bad", deny=True)
scope.set_identity("onebot11", user_id="spam_*", deny=True)    # 键支持 glob / re: 正则
scope.set_identity("onebot11", bot_id="123456", session_id="g9", allow=True)

# 读 / 删
scope.get_identity("onebot11", user_id="u_bad")   # {"deny": True}
scope.delete_identity("onebot11", user_id="u_bad")

③ 出站维度

# 设置限制规则(allow: str|list;deny: bool|str|list;整规则替换语义)
scope.set_action("MyModule", "send", deny=True)                    # 全禁发送
scope.set_action("MyModule", "send", allow=["Text"])               # 仅允许发文本
scope.set_action("MyModule", "api", deny=["set_*", "leave_*"])     # 禁管理类 API

# 读 / 删
scope.get_action("MyModule", "send")       # {"allow": ["Text"]} 原始规则
scope.delete_action("MyModule", "send")    # 移除单动作
scope.delete_action("MyModule")            # 移除该模块全部动作限制

通用

scope.get("platforms")   # 字典式兜底:点分路径读任意节
scope.topology()         # 全量配置树(供 Dashboard)
scope.stats()
# {"module_calls": .., "module_filtered": .., "identity_checks": .., "identity_denied": ..,
#  "action_checks": .., "action_denied": .., "cache_hits": .., "cache_misses": ..}
scope.reset_stats()
scope.clear()           # 清空全部配置(仅内存生效)

高级:字典式点分路径兜底

维度化方法覆盖日常场景;需要直达任意节点(或未来新增的维度)时, 可用字典式 API——get / set / delete 接受点分路径(dict 深合并、写后立读), 并提供 scope[path] / scope[path] = v / del scope[path] / path in scope 协议:

scope.set("bots.onebot11.123456", {"modules": ["Chat"], "blocked": []})
scope.set("identity.users.onebot11.u_bad", {"deny": True})
scope.get("actions.MyModule.send")

scope["platforms.onebot11"]        # 读(不存在抛 KeyError)
scope["platforms.onebot11"] = {...}  # 写
del scope["platforms.onebot11"]      # 删
"actions.MyModule" in scope          # 存在性

拦截可观测:scope.blocked 事件

作用域拦截(模块过滤 / 身份拒绝)发生时会广播生命周期事件 **scope.blocked**, 让"谁被拦、在哪一层被拦"可订阅、可统计、可在 Dashboard 呈现——拦截默认 不回复,但不再是不可知的黑盒。

字段 说明
dimension "module"(模块维度过滤)/ "identity"(身份准入拒绝)
module 被过滤的模块名(仅模块维度)
platform / bot_id / session_id / user_id 拦截发生的来源上下文
from ErisPulse.Core.lifecycle import lifecycle

@lifecycle.on("scope.blocked")
def on_blocked(data):
    print(f"已拦截:{data['dimension']} {data.get('module') or data.get('user_id')}")

缓存与热更新

配置格式校验

加载 / 热更新时逐节校验配置格式:类型错误的节(如 platforms 写成了字符串)、 非法的出站规则(如 allow 写成数字)、未知动作名、未知的顶层键(如 alow 拼写错误) 会输出 WARNING 并忽略对应节 / 条目,其余合法配置照常生效——写错不再静默失效。

常见问题与注意事项

1. 配置层级与覆盖

2. 模块/命令没反应

先怀疑作用域而不是模块本身:

from ErisPulse import sdk

print(sdk.scope.is_allowed(event.get_platform(), bot_id, "MyModule", session_id))
print(sdk.scope.is_identity_allowed(event.get_platform(), bot_id, session_id, user_id))
print(sdk.scope.stats())   # module_filtered / identity_denied > 0 说明有拦截记录

被过滤默认不回复(模块维度与身份维度,避免暴露规则),但会广播 scope.blocked 生命周期事件、统计持续累计; 命令维度被 ACL 拒绝会显式回复"权限不足"。

3. 出站动作被拒时排查

from ErisPulse import sdk

print(sdk.scope.get("actions.MyModule"))
print(sdk.scope.stats())   # action_denied > 0 说明有调用被拦截

拦截是显式的:被拒调用返回 retcode = 34601 的标准失败响应(不发起网络请求)。

4. 会话标识跨平台隔离

(platform, session_id) 组合才是唯一标识。scope.sessions.onebot11."789" 只作用于 onebot11,不影响 telegram 上同为 789 的会话。身份维度的用户键同理。

拓扑树 API

ModuleManager.get_topology() 与 AdapterManager.get_topology() 提供模块/适配器归属关系数据, sdk.get_topology() 一键聚合(含作用域 scope):

from ErisPulse import sdk

topology = sdk.get_topology()
# {
#   "modules": {                                   # 模块 → 拥有的资源
#     "Chat": {
#       "loaded": True, "enabled": True,
#       "commands": ["chat", "translate"],
#       "handlers": {"message": 2, "notice": 1},
#       "routes": {"http": ["/Chat/api"], "ws": [], "sse": []},
#       "lifecycle_hooks": 3,
#     }
#   },
#   "adapters": {                                  # 适配器 → Bot → 作用域
#     "onebot11": {
#       "status": "started", "enabled": True,
#       "bots": {"123456": {"status": "online", "scope": {...}}},
#       "scope": {"modules": [...], "blocked": [...]},
#     }
#   },
#   "scope": {                                     # 作用域(模块 / 身份 / 出站动作)
#     "platforms": {...}, "bots": {...}, "sessions": {...},
#     "identity": {"adapters": {...}, "bots": {...}, "sessions": {...}, "users": {...}},
#     "actions": {...},
#   },
# }