Skip to main content

plugin_switches/
plugin_switches.rs

1//! 端到端的分层插件开关,bot 作者怎么接它。
2//!
3//! 声明一个插件(`demo`),带两个触发器,外加一对管理命令(`/disable <key>` /
4//! `/enable <key>`),它们经 `State<EnabledSet>` 句柄在当前 peer 翻开关。管理命令设了
5//! `can_disable=false`,所以它们永远不会把*自己*关掉(你总能把东西再开回来)。
6//!
7//! 开关 key 遵循 `plugin!{}` + `#[command(id=…)]` 的层级:
8//!   * 插件 master 就是它的 `key` —— 这里是 `demo`;
9//!   * 每个触发器是 `"<plugin_key>.<id>"` —— 这里是 `demo.hello` / `demo.world`。
10//!
11//! 禁用 master 会压住其下每个触发器;禁用某个触发器不影响它的同级。
12//!
13//! 运行(需要一个在线的 OneBot 端点):
14//!   cargo run --example plugin_switches --features onebot
15
16use nagisa::prelude::*;
17
18// 一个插件。它的 master 开关 key 是 "demo";下面两个触发器都挂在它下面。
19nagisa::plugin! { name = "演示插件", category = Tool, key = "demo" }
20
21#[command("hello", id = "hello")]
22async fn hello(reply: Reply) -> HandlerResult {
23    reply.text("hello!").await?;
24    Ok(())
25}
26
27#[command("world", id = "world")]
28async fn world(reply: Reply) -> HandlerResult {
29    reply.text("world!").await?;
30    Ok(())
31}
32
33// ── 管理命令 ────────────────────────────────────────────────────────────────
34//
35// `/disable <key>` 和 `/enable <key>` 在*当前* peer 翻一个开关。`can_disable=false` 保证管理
36// 命令自身永远在线,这样 bot 永远不会被锁死、再也开不回东西。
37//
38// `<key>` 是任意开关 key:插件 master("demo")或某个触发器("demo.hello")。
39
40#[command("/disable", id = "disable", can_disable = false)]
41async fn disable(reply: Reply, es: State<EnabledSet>, peer: EventPeer, args: ArgText) -> HandlerResult {
42    let key = args.0.trim();
43    if key.is_empty() {
44        reply.text("用法:/disable <key>").await?;
45        return Ok(());
46    }
47    // `State<EnabledSet>` deref 到 `EnabledSet`,所以 `es.set(..)` 直接调通。
48    es.set(key, Some(peer.0), false);
49    // 引用触发命令,让确认消息挂在它下面。
50    reply.reply(format!("已在本会话禁用:{key}")).await?;
51    Ok(())
52}
53
54#[command("/enable", id = "enable", can_disable = false)]
55async fn enable(reply: Reply, es: State<EnabledSet>, peer: EventPeer, args: ArgText) -> HandlerResult {
56    let key = args.0.trim();
57    if key.is_empty() {
58        reply.text("用法:/enable <key>").await?;
59        return Ok(());
60    }
61    es.set(key, Some(peer.0), true);
62    reply.reply(format!("已在本会话启用:{key}")).await?;
63    Ok(())
64}
65
66#[cfg(feature = "onebot")]
67#[tokio::main]
68async fn main() -> Result<()> {
69    let shutdown = ctrl_c_shutdown();
70    App::new()
71        // 观察每一次开关改动 —— 在这里把快照持久化到你的存储。
72        .on_switch_change(|key, peer, value| {
73            println!("switch {key} {peer:?} -> {value}");
74        })
75        .run_onebot(OneBotConfig::new("ws://127.0.0.1:8080/onebot/v11/ws"), shutdown)
76        .await
77}
78
79// 没有适配器 feature 时无可运行;用一个空操作的 `main` 让示例仍能编译(上面的 handler 也仍被
80// 类型检查)。
81#[cfg(not(feature = "onebot"))]
82fn main() {
83    println!("build with --features onebot to run this example against a OneBot endpoint");
84}