Skip to main content

nagisa/
lib.rs

1//! Nagisa —— 统一在 OneBot v11 与 Milky 之上的 Rust 聊天机器人框架(门面 crate)。
2//!
3//! 这是面向使用者的唯一入口。bot 作者只依赖 `nagisa`(按需开 `onebot` / `milky`
4//! feature),写 `use nagisa::prelude::*;`,从不直接点名底层的 engine / types /
5//! macros / adapter 这些 crate:
6//!
7//! ```rust,ignore
8//! use nagisa::prelude::*;
9//!
10//! #[command("ping", mention_me)]
11//! async fn ping(reply: Reply) -> HandlerResult {
12//!     reply.text("pong").await?;
13//!     Ok(())
14//! }
15//!
16//! #[tokio::main]
17//! async fn main() -> Result<()> {
18//!     // ctrl_c_shutdown() 起一个后台任务,收到 SIGINT/Ctrl-C 时取消 shutdown
19//!     // token,让 bot 停机。
20//!     let shutdown = ctrl_c_shutdown();
21//!     App::new()
22//!         .run_onebot(OneBotConfig::new("ws://127.0.0.1:8080/onebot/v11/ws"), shutdown)
23//!         .await
24//! }
25//! ```
26//!
27//! # 门面做了什么
28//!
29//! - 把 engine([`nagisa_core`])、统一领域模型([`nagisa_types`])、宏
30//!   ([`nagisa_macros`])在 crate 根重新导出,业务代码只点 `nagisa::…`。
31//! - 精选一个 [`prelude`] —— `use nagisa::prelude::*;` 这一行 glob 导入,带进宏、
32//!   [`App`] 构建器、[`Bot`] 句柄、常用 `FromContext` 提取器、统一的
33//!   `Segment` / `Message` / `Peer` / `Uin` / `Result` / `Error`,以及门控/规则
34//!   组合子。
35//! - 协议适配器(`OneBotConfig` / `MilkyConfig`)挂在 `onebot` / `milky` feature
36//!   后面,日志工具箱(`nagisa::log`)挂在 `log` feature 后面,排版引擎
37//!   (`nagisa::render` —— 文档排版成图片)挂在 `render` feature 后面。
38//! - 提供 [`App`] 构建器([`app`]),把适配器、分发循环、自动登记的 handler 和一个
39//!   [`ShutdownToken`] 接到一起。
40//!
41//! 适配器作者用的管线(新协议适配器要实现的出站动作 trait)在 [`adapter`];
42//! engine 内部的对象安全机制在 [`engine`]。业务代码两者都不点名。
43#![forbid(unsafe_code)]
44
45pub mod app;
46
47pub use app::App;
48
49// ── Engine 层(nagisa-core) ────────────────────────────────────────────────────
50// 分发引擎、`Bot` 句柄、两个传输 trait、`FromContext` 提取器、matcher DSL、注册表
51// 和服务生命周期。在 crate 根重新导出,业务代码不直接点 `nagisa_core` 路径。
52pub use nagisa_core::{
53    // 出站消息日志器:每条 bot 发出的消息都会喂给已注册的日志器(多订阅)。
54    add_outgoing_logger,
55    awake,
56    awake_silent,
57    // 注册表(inventory)+ 插件元数据。`registered_triggers` 报的是已解析到插件的点分 key
58    // (宏先发空 key,解析期按 module_path 关联补上)。
59    collect_into,
60    from_user,
61    group_admin,
62    group_only,
63    group_owner,
64    in_group,
65    keyword,
66    private,
67    registered_plugins,
68    registered_triggers_resolved as registered_triggers,
69    replying,
70    // 分发引擎入口。
71    run_dispatch,
72    superuser,
73    // 总闸 / 休眠的叶子构造器 + 否决时的回复通道。
74    switch,
75    to_me,
76    // 事件触发器(`#[event(Kind)]`):EventKind 选择器 + 细粒度的带类型事件提取器(只有
77    // Nudge 做自身事件过滤 —— Recall 原样投递,让 handler 自己决定防撤回策略)。
78    AdminChange,
79    // 带类型的命令参数解析(Args<T> 类型;#[derive(Args)] 是宏)。
80    ArgError,
81    // 参数元数据(#[derive(Args)] 生成,供 help 自动生成用法)。
82    ArgKind,
83    ArgSpec,
84    ArgText,
85    ArgToken,
86    Args,
87    ArgsMeta,
88    // 在匹配链上取元素的提取器,与 waiter 共用同一套类型:同一个 `Image` 既读内联图片槽,
89    // 也是 `recv::<Image>` 的返回(「内联或追问是同一类型」)。`Option<At>`/`Option<Image>`
90    // 即可选。
91    At,
92    // `Bot` 句柄。它的固有方法就是业务调用的动作面;支撑这些方法、由适配器实现的 trait
93    // 在 [`adapter`]。
94    Bot,
95    BotOffline,
96    BotOnline,
97    Captures,
98    Category,
99    // 冷却门控(默认 UserGlobal、窗口内 max_exec)+ 命令式的 Cd 注入句柄。
100    Cd,
101    CdKey,
102    Command,
103    CommandArg,
104    // 生命周期事件提取器(`#[event(Connect/Disconnect/Ready/BotOnline/BotOffline/Heartbeat)]`)。
105    Connect,
106    Cooldown,
107    CooldownScope,
108    CooldownStore,
109    // 每事件上下文 + 提取器(FromContext)。
110    Ctx,
111    // 开发/诊断标记(配 `App::debug()`):把静默跳过变成 `[dev]` WARN,并对 `Args<T>`
112    // 解析失败自动回一条用法提示。
113    DevMode,
114    Disconnect,
115    // 运行期命令开关。
116    EnabledOverrides,
117    EnabledSet,
118    EventKind,
119    EventPeer,
120    EventSource,
121    Extracted,
122    // 带类型的具名槽 matcher:HEAD 侧,与 TAIL 侧的 Args<T> 对称(#[derive(Slots)] 是宏)。
123    Flank,
124    // 中断引擎(Session/Waiter/Scope)。
125    FlightGuard,
126    // 洋葱中间件(链路)。
127    Flow,
128    FriendAdd,
129    // 带类型的目标选择器(bot.group(g).member(u).mute(..))。
130    FriendRef,
131    FriendRequest,
132    FromArg,
133    FromContext,
134    FromSlots,
135    FromTailText,
136    GateReply,
137    GroupCardChange,
138    GroupJoinRequest,
139    GroupMessage,
140    GroupRef,
141    // Handler 抽象。
142    Handler,
143    HandlerOutcome,
144    HandlerResult,
145    Heartbeat,
146    Honor,
147    Image,
148    KillSwitch,
149    LuckyKing,
150    // Matcher(一个 regex 支撑的触发器;command([..]) 是字面量糖,slots(..) 是带类型的头部)。
151    Matcher,
152    MemberJoin,
153    MemberLeave,
154    MemberRef,
155    Middleware,
156    Mute,
157    NamedCaptures,
158    Next,
159    Nudge,
160    ParseArgs,
161    ParsedCommand,
162    PluginMeta,
163    PrivateMessage,
164    // 内置限流器(经 App::layer 选用)。
165    RateLimit,
166    RateLimitScope,
167    Ready,
168    Recall,
169    Reject,
170    // 跨会话 rendezvous(token/bind:此处签发,彼处认领)。
171    Rendezvous,
172    RendezvousSnapshot,
173    Replied,
174    Reply,
175    ReplyMsg,
176    Router,
177    // 规则/权限组合代数(& | !)+ 内置规则。
178    Rule,
179    Scope,
180    Sender,
181    // 服务生命周期(Supervisor + DAG)。
182    Service,
183    ServiceBus,
184    Session,
185    // 根取消/停机信号。
186    ShutdownToken,
187    SleepState,
188    SlotSpec,
189    SlotValue,
190    Slots,
191    State,
192    Superusers,
193    Supervisor,
194    SwitchKey,
195    Tail,
196    ToMe,
197    TriggerId,
198    TriggerKind,
199    TriggerMeta,
200    TriggerSpec,
201    WaitFlow,
202    Waiter,
203    WaiterDepth,
204    WaiterStore,
205};
206
207/// 适配器作者用的管线:新协议适配器要实现的出站动作 trait —— `ActionInvoker`
208/// (两协议通用的根)、`OneBotActions`(OneBot v11 加上揉进来的 NapCat / LLOneBot /
209/// Lagrange 厂商扩展)、`MilkyActions`,由 `Actions` marker 合在一起。业务代码从不
210/// 点名这些 —— 它调 [`Bot`] 的固有方法,厂商专属动作走 `bot.actions().<m>()` ——
211/// 所以放在这个子模块而非精选的 crate 根。转自 [`nagisa_core::adapter`]。
212pub mod adapter {
213    pub use nagisa_core::adapter::{ActionInvoker, Actions, MilkyActions, OneBotActions};
214}
215
216/// Engine 内部管线:分发引擎据以搭建的对象安全 handler/rule/waiter 机制
217/// (`ErasedHandler`、`Check`、`Selector`、`WaiterBuilder`、`StateMap`)。handler
218/// 作者从不直接点名这些;不进精选的 crate 根。转自 [`nagisa_core::engine`]。
219pub mod engine {
220    pub use nagisa_core::engine::{Check, ErasedHandler, Selector, StateMap, WaiterBuilder};
221}
222
223// `inventory`由 `nagisa_core` 转出;这里再露一层,让 `#[command]` 宏生成的
224// `::nagisa_core::inventory::submit!` 经门面也能解析,插件 crate 无需直接依赖它。
225pub use nagisa_core::inventory;
226
227// `async_trait` 转出:实现自定义 `Service` / `ActionInvoker` 的作者要给 `impl` 标
228// `#[nagisa::async_trait]`,否则会撞上费解的 `E0195`。经门面露出,免去直接依赖
229// `async-trait`。
230pub use nagisa_core::async_trait;
231
232// ── 抽象层(nagisa-types) ──────────────────────────────────────────────────────
233// 跨协议的领域模型:事件、消息、段、实体、id、资源、统一的
234// `Error`/`Result`,以及 `Capability`。
235pub use nagisa_types::capability::{Capability, Protocol};
236pub use nagisa_types::vendor::Vendor;
237// 实体类型 —— `Bot` 方法返回的统一领域结构。handler 若要写自己的 `Result<T>`
238// (如 `nagisa::Result<nagisa::UserInfo>`)需要它们在根可见,所以全部精选到这里。
239pub use nagisa_types::entity::{
240    AiCharacter, AiCharacterGroup, Announcement, Business, EssenceMessage, FileFetch, FileMeta, FriendCategory,
241    FriendCategoryList, FriendGroup, FriendInfo, FriendStatus, GroupFileList, GroupFolder, GroupInfo, HonorList,
242    HonorMember, ImplStatus, MemberInfo, ProfileLiker, Rkey, Role, Sex, UserInfo,
243};
244
245/// `ImplInfo`:`get_impl_info()` 的返回 —— 已连接实现的身份(app 名/版本、QQ 协议
246/// 信息)。定义在 engine crate。厂商判定走 `bot.vendor()`(OneBot 专用轴),不在此类型上。
247pub use nagisa_core::ImplInfo;
248pub use nagisa_types::context::Context;
249pub use nagisa_types::error::{ActionErrorKind, Error, Result, TransportError};
250
251/// `bail!("...")` / `bail!(kind, "...")`:从 handler 里提前返回一个 `nagisa`
252/// [`Error`],对标 `anyhow::bail!`。见 [`Error::action`]。
253pub use nagisa_types::bail;
254pub use nagisa_types::event::{
255    Event, HonorKind, MessageEvent, Meta, Notice, RawEvent, ReactionKind, Request, RequestToken,
256};
257pub use nagisa_types::id::{MessageId, Peer, Scene, Uin};
258pub use nagisa_types::message::{Message, MessageExt, Msg};
259pub use nagisa_types::resource::{Media, ResourceRef, ResourceSource};
260pub use nagisa_types::segment::{Forward, ForwardNode, ImageSubType, Segment};
261
262// 同时把 `nagisa_types` crate 本身露出来,作为罕见的逃生口:业务偶尔需要 prelude
263// 没暴露的名字。
264pub use nagisa_types;
265
266// 把支撑用的 engine crate 以自己的名字露出,好让 `#[command]` / `#[event]` /
267// `plugin!` / `#[derive(Args)]` / `#[derive(ArgEnum)]` 这些宏发出的全限定路径仅经门面
268// 就能解析 —— 守住「bot 作者只依赖 `nagisa`」的承诺。当门面是被依赖的 crate 时,
269// `nagisa-macros` 用 `proc-macro-crate` 把它发出的 `nagisa_core::…` 根改写成
270// `::nagisa::nagisa_core::…`;正是这条重新导出让那些路径点到 engine(`plugin`/`registry`/
271// `args` 子模块本就是 `pub`,深层尾巴也能解析)。与上面 `pub use nagisa_types;` 逃生口对称。
272#[doc(hidden)]
273pub use nagisa_core;
274
275// ── SIGINT 便捷封装 ─────────────────────────────────────────────────────────────
276
277/// 起一个后台任务,收到 SIGINT(Ctrl-C)时取消返回的 [`ShutdownToken`]。
278///
279/// 这是让 bot 可停机最省事的办法:不必自己 new 一个 [`ShutdownToken`] 再接信号
280/// 处理,直接 `ctrl_c_shutdown()` 把结果传给 `run_onebot`/`run_milky` 即可。
281///
282/// ```ignore
283/// #[tokio::main]
284/// async fn main() -> nagisa::Result<()> {
285///     let shutdown = nagisa::ctrl_c_shutdown();
286///     App::new().run_onebot(cfg, shutdown).await
287/// }
288/// ```
289///
290/// 任务会忽略 `tokio::signal::ctrl_c` 的错误(比如平台不支持)—— 那种平台上 token
291/// 只是不会从这个来源触发,你仍可手动取消它。
292pub fn ctrl_c_shutdown() -> ShutdownToken {
293    let token = ShutdownToken::new();
294    let child = token.clone();
295    tokio::spawn(async move {
296        if tokio::signal::ctrl_c().await.is_err() {
297            tracing::warn!("ctrl_c signal listener failed; shutdown will not fire on Ctrl-C");
298        }
299        child.cancel();
300    });
301    token
302}
303
304// ── 宏(nagisa-macros) ─────────────────────────────────────────────────────────
305/// `#[command(...)]` 属性宏:声明式登记一个 matcher 门控的 handler,在 [`App::new`]
306/// 时经 `inventory` 自动收集。
307pub use nagisa_macros::command;
308
309/// `plugin!{ .. }` 宏:把当前模块声明为一个插件。同模块(及更下层)的触发器按
310/// `module_path!()` 关联到它,其分层开关继承插件的 `default_enable`/`can_disable`。
311/// 见插件模型。
312pub use nagisa_macros::plugin;
313
314/// `#[event(Kind, id="..")]` 属性宏:声明式登记一个事件触发的 handler(与
315/// `#[command]` 对等),在给定 [`EventKind`] 的事件上触发,受同一套分层插件开关门控。
316pub use nagisa_macros::event;
317
318/// `#[derive(Args)]`:为结构体派生 [`ParseArgs`],使其可经 [`struct@Args`]`<T>` 从命令
319/// 参数里提取。派生宏与 `Args<T>` 提取器刻意共用 `Args` 这个名字(跨宏/类型命名空间),
320/// 同 serde 的 `Serialize`。
321pub use nagisa_macros::Args;
322
323/// `#[derive(ArgEnum)]`:为无字段枚举派生 [`FromArg`](变体名转小写;支持
324/// `#[arg(rename="..")]`/`#[arg(alias="..")]`),使其可作受限的 `Args<T>` 字段类型
325/// (如 `on|off`)。
326pub use nagisa_macros::ArgEnum;
327
328/// `#[derive(Slots)]`:命令头 = **有序区块序列**——结构体级 `#[slots(lit("查看"), board, lit("榜"),
329/// scope)]`(`lit("..")` 固定块、裸标识符引用同名字段、`sep=`/`usage=` 键),字段各自 `#[slot(re=..)]`
330/// /`#[slot(union=[..])]`/`#[slot(tail)]` 定来源/类型(`Option<T>` 表可选块)。派生 [`FromSlots`],可经
331/// [`struct@Slots`]`<T>` 提取。HEAD 侧,与持有 TAIL 的 `#[derive(Args)]` 对称;还自动生成
332/// `COMMAND_WORDS`(字面 × 各必填 union 块的笛卡尔积)供 help 像字面命令一样枚举。
333pub use nagisa_macros::Slots;
334
335/// `matcher! { lit(".."), <field>: <ty> = re("..")|union("a","b")|tail, sep="..", usage=".." }`:对同一套
336/// `#[derive(Slots)]` codegen 的函数式糖(同样的区块序列),用于写一个内联头部 matcher;求值为
337/// [`Matcher`]。派生宏才是规范形式(它还顺带产出 `Slots<T>` 提取器类型)。
338pub use nagisa_macros::matcher;
339
340// ── 协议适配器(feature 门控) ─────────────────────────────────────────────────
341#[cfg(feature = "onebot")]
342pub use nagisa_onebot;
343#[cfg(feature = "onebot")]
344pub use nagisa_onebot::{OneBotAdapter, OneBotConfig};
345
346#[cfg(feature = "milky")]
347pub use nagisa_milky;
348#[cfg(feature = "milky")]
349pub use nagisa_milky::{MilkyAdapter, MilkyConfig, MilkyMode};
350
351// ── 日志门面(feature 门控) ────────────────────────────────────────────────────
352/// 可选的日志工具箱(`use nagisa::log::*;`),挂在 `log` feature 后面,好让 bot 作者只
353/// 依赖 `nagisa`。转出 [`nagisa_log`]:
354///
355/// - [`render`](nagisa_log::render)`(&Event) -> String` —— 纯函数的可读事件渲染器;
356/// - [`EventLog`](nagisa_log::EventLog) —— 给 `App::on_top` 用的可选 top 观察者(运行期
357///   开关 + 按 kind 过滤);
358/// - [`init`](nagisa_log::init) —— 统一记录器(控制台 + 可选滚动文件 + 按 source/`target`
359///   过滤),同时返回「日志即事件」的 [`LogBus`](nagisa_log::LogBus);
360/// - [`on_record`](nagisa_log::on_record) —— 把 bus 抽到一个异步 sink 里,让业务持久化日志,
361///   与消息 Event/handler 路径分开(防自激)。
362#[cfg(feature = "log")]
363pub use nagisa_log as log;
364
365/// 可选排版引擎(`use nagisa::render::*;`),挂在 `render` feature 后面,好让 bot 作者只
366/// 依赖 `nagisa`。转出 [`nagisa_render`]:把标记文本 / 构建器文档排版成图片字节(PNG / WebP),
367/// 再经既有的 `Segment::image_bytes` 出图。
368#[cfg(feature = "render")]
369pub use nagisa_render as render;
370
371/// bot 作者唯一需要的一行 glob 导入:`use nagisa::prelude::*;`。
372///
373/// 带进:
374/// - 宏 —— `#[command]` / `#[event]` / `plugin!{}` / `#[derive(Args)]` /
375///   `#[derive(ArgEnum)]` / `#[derive(Slots)]` / `matcher!{}` / `bail!`;
376/// - [`App`] 构建器、[`Bot`] 句柄、[`Router`],以及
377///   [`ctrl_c_shutdown`] / [`ShutdownToken`];
378/// - handler 签名按类型点名的常用 `FromContext` 提取器
379///   (`Reply` / `GroupMessage` / `Command` / `ArgText` / `State<T>` / `Session` /
380///   `Sender` / 各 `#[event]` 提取器 …);
381/// - 门控/规则组合子(`to_me` / `group_admin` / `superuser` / … 以及
382///   `switch` / `awake` / `Cooldown`)、中断引擎句柄
383///   (`Session` / `Waiter` / `Scope` / `Rendezvous`)、带类型目标选择器
384///   (`GroupRef` / `MemberRef` / `FriendRef`);
385/// - 统一领域模型(`Segment` / `Message` / `Peer` / `Uin` /
386///   `MessageId` / `Event` / `Result` / `Error` …)。
387///
388/// 适配器配置(`OneBotConfig` / `MilkyConfig` / `MilkyMode`)在对应 feature 开启时
389/// 一并带入。
390pub mod prelude {
391    // 宏:#[command]/#[event] 属性、plugin!{} 声明、
392    // #[derive(Args)]/#[derive(ArgEnum)]/#[derive(Slots)] 派生、matcher!{} 糖,
393    // 以及让 handler 提前失败的 `bail!`。
394    pub use crate::{bail, command, event, matcher, plugin, ArgEnum, Args, Slots};
395
396    // 自定义参数解析:`ParseArgs` trait 及其 token/error 类型,加上字段类型 trait
397    // `FromArg` —— 给手写 `Args<T>` impl(而非 `#[derive(Args)]`)的 handler 用。
398    // `ArgSpec`/`ArgKind`/`ArgsMeta`:参数元数据,help 据此自动生成用法。
399    pub use crate::{ArgError, ArgKind, ArgSpec, ArgToken, ArgsMeta, FromArg, ParseArgs};
400
401    // 带类型的具名槽 matcher(HEAD 侧,与 Args<T> 对称):`Tail` 通配载荷,以及手写头部用的
402    // `FromSlots`/`SlotValue` trait。(`Slots<T>` 本身随上面 `#[derive(Slots)]` 宏一起带入 ——
403    // 类型与派生共用名字,同 `Args`。)
404    pub use crate::{FromSlots, FromTailText, SlotValue, Tail};
405
406    // App + engine 句柄。
407    pub use crate::{ctrl_c_shutdown, App, Bot, Router, ShutdownToken};
408
409    // `#[async_trait]` —— `FromContext`/自定义 async trait 绕不开的搭档
410    // (`#[async_trait] impl FromContext for MyExtractor { async fn from_context(ctx: &Ctx) .. }`)。
411    // 经 glob 露出,自定义提取器作者无需再写一行 import,也无需冗长的 `#[nagisa::async_trait]`。
412    // (crate 根也仍有这条转出。)
413    pub use crate::async_trait;
414
415    // 服务生命周期(业务偶尔会用到)。
416    pub use crate::{Service, ServiceBus, Supervisor};
417
418    // handler 签名按类型点名的常用提取器,加上自定义 `FromContext` impl 作者会用到的
419    // 提取结果类型(`Extracted`/`Reject`)。
420    pub use crate::{
421        ArgText, At, Captures, Command, CommandArg, Ctx, EventPeer, Extracted, FromContext, GroupMessage,
422        HandlerResult, Image, PrivateMessage, Reject, Reply, ReplyMsg, Sender, State, ToMe,
423    };
424
425    // 规则/权限组合子 + 内置规则(用 `&` / `|` / `!` 组合)。`Superusers` 是 `superuser()`
426    // 背后注入的集合 —— 需要手动判断(比如按是否 superuser 分支而非直接门控)的 handler 经
427    // `State<Superusers>` 点名它。
428    pub use crate::{
429        awake, awake_silent, from_user, group_admin, group_only, group_owner, in_group, keyword, private, replying,
430        superuser, switch, to_me, GateReply, KillSwitch, Rule, SleepState, Superusers,
431    };
432
433    // 冷却门控(经 `Cooldown::into_rule` 变成 `Rule`)+ 命令式的 `Cd` 注入句柄。
434    pub use crate::{Cd, CdKey, Cooldown, CooldownScope, CooldownStore, TriggerId};
435
436    // 洋葱中间件。
437    pub use crate::{Flow, Middleware, Next, RateLimit, RateLimitScope};
438
439    // 中断引擎(Session/Waiter/Scope)。
440    pub use crate::{Rendezvous, Replied, Scope, Session, WaitFlow, Waiter};
441
442    // 带类型的目标选择器。
443    pub use crate::{FriendRef, GroupRef, MemberRef};
444
445    // 插件元数据(给 help/菜单插件用)+ 运行期开关。
446    pub use crate::{
447        collect_into, registered_plugins, registered_triggers, Category, EnabledSet, EventKind, PluginMeta, SwitchKey,
448        TriggerKind, TriggerMeta, TriggerSpec,
449    };
450
451    // 事件触发提取器(`#[event(Kind)]` handler 签名按类型点名),含生命周期:
452    // Connect/Disconnect(传输)、Ready(框架)、BotOnline/BotOffline(账号)、Heartbeat。
453    pub use crate::{
454        AdminChange, BotOffline, BotOnline, Connect, Disconnect, FriendAdd, FriendRequest, GroupCardChange,
455        GroupJoinRequest, Heartbeat, Honor, LuckyKing, MemberJoin, MemberLeave, Mute, Nudge, Ready, Recall,
456    };
457
458    // 统一领域模型。
459    pub use crate::{
460        ActionErrorKind, Capability, Context, Error, Event, Forward, ForwardNode, FriendInfo, GroupInfo, Media,
461        MemberInfo, Message, MessageEvent, MessageExt, MessageId, Msg, Notice, Peer, Protocol, ReactionKind, Request,
462        ResourceSource, Result, Scene, Segment, Uin,
463    };
464
465    // 适配器配置(feature 门控)。
466    #[cfg(feature = "onebot")]
467    pub use crate::OneBotConfig;
468    #[cfg(feature = "milky")]
469    pub use crate::{MilkyConfig, MilkyMode};
470
471    // 协议/厂商专属动作 trait:让 `bot.actions().<method>()` 直达口的方法在作用域内可解析
472    // (`Actions` 是组合 marker,`OneBotActions`/`MilkyActions` 提供实际方法)。
473    pub use crate::adapter::{Actions, MilkyActions, OneBotActions};
474}