Skip to main content

nagisa_onebot/adapter/
config.rs

1//! 适配器配置:传输模式 + `OneBotConfig` builder。
2use serde_json::Value;
3use std::net::SocketAddr;
4use std::sync::Arc;
5use std::time::Duration;
6
7/// OneBot 收发传输模式。`Forward` 是默认(nagisa 作为 WS 客户端主动外连)。`ReverseWs` 让
8/// nagisa 当 WS **服务端**,由协议端连进来。`Http` 把 HTTP-POST 事件上报与 HTTP API 耦在一起;
9/// `HttpApi` 只是其中的动作半边(无 webhook)。
10#[derive(Clone, Debug)]
11pub enum OneBotTransport {
12    /// nagisa 主动外连一个正向 WS 端点(既有默认)。
13    Forward { url: String },
14    /// nagisa 绑一个 TCP 监听器,在 `path` 上接受 WS 升级。
15    ReverseWs { bind: SocketAddr, path: String },
16    /// nagisa 经 `post_bind`+`post_path` 的 HTTP POST 收事件,并把动作发往 `api_url`。
17    /// `secret`(若设)校验 `X-Signature: sha1=<hmac>`。
18    Http { api_url: String, post_bind: SocketAddr, post_path: String, secret: Option<String> },
19    /// 纯 HTTP-API **动作**客户端——把动作 POST 到 `api_url`,**不**附带 HTTP-POST webhook。
20    /// 让「HTTP 动作 + WS 事件」可通过把本动作传输与独立的 `Forward`/`ReverseWs` 事件适配器
21    /// 配对来表达(动作信道与事件信道可不同)。作为 [`EventSource`](nagisa_core::EventSource),
22    /// 本模式不发任何事件;`run()` 空转直到 shutdown。
23    HttpApi { api_url: String },
24    /// LLOneBot 私有 HTTP 客户端:actions POST 到 `api_url`,事件经 `events`
25    /// 选定的拉取方式获取(SSE `/_events` 推送流 或 `get_event` 长轮询)。无公网
26    /// 回调 / 无 WS 时也能收事件,并与 webhook / forward-WS 走同一解码+分发路径。
27    LLOneBotHttp { api_url: String, events: LLOneBotEventMode },
28}
29
30/// LLOneBot 纯 HTTP 客户端的事件拉取方式([`OneBotTransport::LLOneBotHttp`])。
31#[derive(Clone, Debug)]
32pub enum LLOneBotEventMode {
33    /// 订阅 SSE `/_events` 流(推送式,优先):服务端有事件即下发,断流自动重连。
34    Sse,
35    /// `get_event` 长轮询(回退式):按 `interval` 周期反复排空后端事件队列。
36    LongPoll { interval: Duration },
37}
38
39/// 适配器配置。端点 / 绑定完全落在 `mode` 里(如默认正向 WS 模式的
40/// `OneBotTransport::Forward { url }`)。
41#[derive(Clone, Debug)]
42pub struct OneBotConfig {
43    pub access_token: Option<String>,
44    /// 传输模式。默认 `Forward { url }`。
45    pub mode: OneBotTransport,
46}
47
48impl OneBotConfig {
49    /// 正向 WS 配置(默认模式),如 `ws://127.0.0.1:8080/onebot/v11/ws`。
50    pub fn new(url: impl Into<String>) -> Self {
51        OneBotConfig { access_token: None, mode: OneBotTransport::Forward { url: url.into() } }
52    }
53    /// 反向 WS 配置:nagisa 绑 `bind`,在 `path` 上接受 WS 升级。
54    pub fn reverse_ws(bind: SocketAddr, path: impl Into<String>) -> Self {
55        OneBotConfig { access_token: None, mode: OneBotTransport::ReverseWs { bind, path: path.into() } }
56    }
57    /// HTTP-POST 配置:事件 POST 到 `post_bind`+`post_path`,动作发往 `api_url`。
58    pub fn http(api_url: impl Into<String>, post_bind: SocketAddr, post_path: impl Into<String>) -> Self {
59        OneBotConfig {
60            access_token: None,
61            mode: OneBotTransport::Http {
62                api_url: api_url.into(),
63                post_bind,
64                post_path: post_path.into(),
65                secret: None,
66            },
67        }
68    }
69    /// 纯 HTTP-API 动作配置:动作 POST 到 `api_url`,**不**起 webhook。与独立的纯事件适配器
70    /// (`Forward`/`ReverseWs`)配对,即可实现「HTTP 动作 + WS 事件」的拆分。
71    pub fn http_api(api_url: impl Into<String>) -> Self {
72        OneBotConfig { access_token: None, mode: OneBotTransport::HttpApi { api_url: api_url.into() } }
73    }
74    /// LLOneBot HTTP 客户端 + SSE `/_events` 事件流:actions POST 到 `api_url`,
75    /// 事件经 SSE 推送(首选)。无公网回调即可收事件。
76    pub fn llonebot_http_sse(api_url: impl Into<String>) -> Self {
77        OneBotConfig {
78            access_token: None,
79            mode: OneBotTransport::LLOneBotHttp { api_url: api_url.into(), events: LLOneBotEventMode::Sse },
80        }
81    }
82    /// LLOneBot HTTP 客户端 + `get_event` 长轮询事件源:actions POST 到 `api_url`,
83    /// 每 `interval` 拉一批排队事件。SSE 不可用时的回退方案。
84    pub fn llonebot_http_long_poll(api_url: impl Into<String>, interval: Duration) -> Self {
85        OneBotConfig {
86            access_token: None,
87            mode: OneBotTransport::LLOneBotHttp {
88                api_url: api_url.into(),
89                events: LLOneBotEventMode::LongPoll { interval },
90            },
91        }
92    }
93    pub fn with_token(mut self, token: impl Into<String>) -> Self {
94        self.access_token = Some(token.into());
95        self
96    }
97    /// 设置 HTTP-POST 的 `X-Signature` 密钥(仅 `Http` 模式有意义)。
98    pub fn with_secret(mut self, secret: impl Into<String>) -> Self {
99        if let OneBotTransport::Http { secret: s, .. } = &mut self.mode {
100            *s = Some(secret.into());
101        }
102        self
103    }
104}
105
106/// 快速操作应答器:给定解码后的 [`Event`](nagisa_types::event::Event),可选地返回一个 JSON
107/// 正文,回发给协议端以替代默认的 `204`。
108/// 见 [`OneBotAdapter::with_quick_op`](super::OneBotAdapter::with_quick_op) 与 [OneBot v11 §6.1 quick-op](https://github.com/botuniverse/onebot-11/blob/master/communication/http.md)。
109pub type QuickOpFn = Arc<dyn Fn(&nagisa_types::event::Event) -> Option<Value> + Send + Sync>;
110
111/// 对 `access_token` 查询值做最小百分号编码:保留 RFC 3986 的非保留字符,其余一律 `%XX`
112/// 转义。免得为罕见的非字母数字 token 引入一个 url crate。
113pub(crate) fn encode_query(s: &str) -> String {
114    let mut out = String::with_capacity(s.len());
115    for b in s.bytes() {
116        match b {
117            b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => out.push(b as char),
118            _ => out.push_str(&format!("%{b:02X}")),
119        }
120    }
121    out
122}