简体中文 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 >!--}

Matching Entry Syntax (Unified Across the System)

All "name lists" in scope (module names, identity keys, outbound entries) share the same matching syntax (via ErisPulse.Core.text_match):

Syntax Example Description
Exact name "Chat" Full value comparison, case-insensitive
glob "Tool*"、"spam_*" * for any string / ? for a single character / [seq] for character set, case-insensitive
Regular expression "re:^Danger.*" Declared with re: prefix, matches using regular expression search, default case-insensitive

Global Fallback: default_allow

default_allow is the single global fallback switch (default true), affecting two decision dimensions:

Setting it to false enables "implicit deny" strict mode: whitelist-style management, everything not explicitly allowed is denied.

Exception: The outbound dimension is not affected by default_allow—it is an independent tightening switch, defaulting to full allow, with restrictions only via explicit rules (framework-level owner-empty calls are always allowed). This strict global mode won't accidentally cut off all module message replies. Command ACL has a separate ErisPulse.event.command.default_allow fallback, independent of others.

Configuration File

[ErisPulse.scope]
default_allow = true        # Global fallback (false = implicit deny strict mode)
cache_size = 1024           # LRU cache size

# ── ① Module dimension (priority: session > Bot > platform) ──
[ErisPulse.scope.platforms.onebot11]
modules = ["Chat", "Tool*"]   # Whitelist: exact name / glob / re: regex
blocked = ["re:^Danger"]
[ErisPulse.scope.bots.onebot11."123456"]
modules = ["Chat"]
merge = true                  # Append on top of platform-level binding (default is full override)
[ErisPulse.scope.sessions.onebot11."789012345"]
modules = ["Chat"]

# ── ② Identity dimension (priority: user > session > Bot > adapter) ──
[ErisPulse.scope.identity.adapters.onebot11]
deny = true                   # Discard all events from this adapter
[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"]           # User keys support glob / re: regex
deny = ["u_bad", "spam_*"]

# ── ③ Outbound dimension (default allow all, restrict only via explicit rules) ──
[ErisPulse.scope.actions.MyModule]
send = { deny = true }                                    # Disable all sending
api = { allow = ["get_*"] }                               # Allow only query-type standard APIs
request = { deny = true }                                 # Disable request handling

① Модульный уровень

Отвечает на вопрос: "Какие модули доступны в определённом контексте?" По умолчанию все модули открыты; фильтрация начинается только после привязки конфигурации.
Модули и адаптеры не требуют каких-либо изменений.

flowchart TD
    A["Событие достигает обработчика/команды модуля"] --> B{"scope.is_allowed<br/>(platform, bot, module, session)"}
    B --> C{"Анализ цепочки: сессионный уровень > уровень бота > уровень платформы<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                    # Фактически для этого бота = ["Chat", "Tool", "Music"]

② Identity Dimension (Event Admission)

Answers "whose events are received." Events that are denied are completely discarded at the distribution entry— they do not enter middleware or any processor (including framework-level), with only TRACE-level logs visible (core.scope.identity_denied).

[ErisPulse.scope.identity.adapters.onebot11]
deny = true
[ErisPulse.scope.identity.users.onebot11]
allow = ["u_admin"]   # Even if adapter-level denied, events from u_admin are still allowed

③ Outbound Dimension (Limiting Module Outbound Calls)

Constraints on modules initiating outbound actions: message sending / standard API actions / request operations. Three types of actions correspond to the underlying DSL: Event.reply and Send (send), Api / call_api (api), and Request's accept/reject (request). Outbound calls initiated by modules during event handler execution carry the module owner, and are uniformly judged by this dimension.

Rule Forms (Inline Tables)

Each action's rule is an inline table: { allow = [...], deny = true|[...] }. Only one rule per action is allowed (TOML keys cannot repeat, choose between full deny and fine-grained):

[ErisPulse.scope.actions.MyModule]
send = { deny = true }                                  # Disable all sending (Event.reply / Send DSL)
# Or fine-grained method-level: send = { allow = ["Text", "Image*"], deny = ["File"] }
api = { allow = ["get_*"] }                             # Allow only query-type standard APIs
# Or action-level blacklist: api = { deny = ["set_*", "leave_*"] }
request = { deny = true }                               # Disable request handling accept/reject

Decision Semantics

Default is full allow—unconfigured, or owner is empty (internal framework calls) are allowed. After configuration, rules are evaluated in the following order:

  1. deny = true → deny
  2. deny list matches call name → deny
  3. allow list is non-empty and call name is not matched (or call has no name) → deny
  4. Otherwise, allow

Denied calls do not initiate any network requests, directly returning a standard failure response (retcode = 34601, see api-response §5.3).

# Runtime API
sdk.scope.set_action("MyModule", "send", deny=True)              # Disable all message sending
sdk.scope.set_action("MyModule", "send", allow=["Text"])         # Allow only text sending
sdk.scope.is_action_allowed("MyModule", "send", name="Image")    # False
sdk.scope.is_action_allowed("MyModule", "api", name="get_user_info")  # Evaluate by rules
sdk.scope.delete_action("MyModule", "send")                      # Re-enable
sdk.scope.get_action("MyModule", "send")                         # Current rule for this action

Runtime API

The runtime API for scope is divided into three layers: decision (three questions), dimension-specific read/write (per dimension set / get / delete parameterized methods, fully type-annotated, IDE can auto-complete), and dictionary-style fallback (dot-separated path to reach any section).

from ErisPulse import sdk

scope = sdk.scope

Decision (Three Questions)

scope.is_allowed("onebot11", "123456", "Chat")                 # ① Module dimension
scope.is_allowed("onebot11", "123456", "Chat", "789012345")    # With session-level
scope.is_allowed("onebot11", "123456", None)                   # Framework-level resource -> True

scope.is_identity_allowed("onebot11", "123456", "group_9", "u1")   # ② Identity dimension

scope.is_action_allowed("MyModule", "send")                    # ④ Outbound dimension
scope.is_action_allowed("MyModule", "send", name="Image")      # Fine-grained method-level

① Module Dimension

# Binding (hierarchy determined by parameters: session_id > bot_id > platform-level)
scope.set_module("onebot11", bot_id="123456", modules=["Chat", "Tool*"])
scope.set_module("onebot11", blocked=["re:^Danger"])                       # Platform-level
scope.set_module("onebot11", bot_id="123456", session_id="g9", modules=["Chat"])  # Session-level
scope.set_module("onebot11", bot_id="123456", modules=["Music"], merge=True)      # Union with existing entries
scope.set_module("onebot11", bot_id="123456", modules=["Chat"], persist=False)    # Runtime only

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

merge=True is write-time union (merges entries with existing bindings at this level); merge = true configuration keys at parse time across levels are described above in [Binding Inheritance](#binding inheritance-merge)—these are independent mechanisms.

Runtime binding (persist=False) semantics: Runtime bindings are saved in a separate overlay layer, any subsequent configuration writes / hot reloads of configuration files will not overwrite them (the configuration tree is rebuilt and automatically reapplied in order, including runtime deletions). They are not persisted, and are lost after process restart; when a module is unloaded, runtime bindings written by that module are cleaned up. Subsequent persist=True writes (user-persistent semantics) to the same path will replace runtime rules.

② Identity Dimension

# Binding policies (hierarchy determined by parameters: user > session > bot > adapter; allow / deny are mutually exclusive)
scope.set_identity("onebot11", user_id="u_bad", deny=True)
scope.set_identity("onebot11", user_id="spam_*", deny=True)    # Key supports glob / re: regex
scope.set_identity("onebot11", bot_id="123456", session_id="g9", allow=True)

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

③ Outbound Dimension

# Set restriction rules (allow: str|list; deny: bool|str|list; whole rule replacement semantics)
scope.set_action("MyModule", "send", deny=True)                    # Disable all sending
scope.set_action("MyModule", "send", allow=["Text"])               # Allow only text sending
scope.set_action("MyModule", "api", deny=["set_*", "leave_*"])     # Disable management-type APIs

# Read / Delete
scope.get_action("MyModule", "send")       # {"allow": ["Text"]} original rule
scope.delete_action("MyModule", "send")    # Remove single action
scope.delete_action("MyModule")            # Remove all action restrictions for this module

General

scope.get("platforms")   # Dictionary-style fallback: dot-separated path to read any section
scope.topology()         # Full configuration tree (for Dashboard)
scope.stats()
# {"module_calls": .., "module_filtered": .., "identity_checks": .., "identity_denied": ..,
#  "action_checks": .., "action_denied": .., "cache_hits": .., "cache_misses": ..}
scope.reset_stats()
scope.clear()           # Clear all configurations (memory only)

Advanced: Dictionary-style Dot-Path Fallback

Dimension-specific methods cover daily scenarios; when you need to directly access any node (or future added dimensions), use the dictionary-style API—get / set / delete accepts dot-separated paths (deep dict merge, immediate read after write), and provides scope[path] / scope[path] = v / del scope[path] / path in scope protocols:

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"]        # Read (raises KeyError if not exists)
scope["platforms.onebot11"] = {...}  # Write
del scope["platforms.onebot11"]      # Delete
"actions.MyModule" in scope          # Existence check

Наблюдение за перехватом: событие 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')}")

Cache and Hot Reload

Configuration Format Validation

During loading / hot reload, configuration format is validated per section: type errors (e.g. platforms written as a string), invalid outbound rules (e.g. allow written as a number), unknown action names, unknown top-level keys (e.g. alow with a typo) will output WARNING and ignore the corresponding section / entry, while other valid configurations remain effective—incorrect writing no longer silently fails.

Часто задаваемые вопросы и замечания

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 и не влияет на сессию с таким же 789 в telegram. То же самое касается ключей пользователей в иерархии идентификации.

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": {...},
#   },
# }