Skip to main content

nagisa_core/
session.rs

1//! 中断引擎:`Session`/`Waiter`——事件通用、作用域可组合的条件监听。
2//! 支持多轮(一个 waiter 经 `mpsc` 收多条事件)、群内多人(作用域不绑死发送者)、
3//! 嵌套(深者优先),以及内层依赖注入(收到的事件重建成新 `Ctx`);分发优先级三层、
4//! 由 router 保证(top > waiter > 默认 handler)。
5use crate::bot::Bot;
6use crate::ctx::{Ctx, StateMap};
7use crate::event_trigger::EventKind;
8use crate::extract::{Extracted, FromContext, Reject, Reply};
9use async_trait::async_trait;
10use nagisa_types::event::Event;
11use nagisa_types::message::MessageExt;
12use nagisa_types::prelude::*;
13use std::any::TypeId;
14use std::collections::HashSet;
15use std::future::Future;
16use std::sync::atomic::{AtomicU64, Ordering};
17use std::sync::{Arc, Mutex};
18use std::time::Duration;
19use tokio::sync::mpsc;
20use tokio::sync::mpsc::error::TrySendError;
21use tokio::time::Instant;
22
23/// 用户提供的候选事件谓词,作为 [`Selector`] 的兜底维度。起别名是为了让
24/// `Selector::filter` 避开 `clippy::type_complexity`。
25pub type EventFilter = Arc<dyn Fn(&Event) -> bool + Send + Sync>;
26
27/// 未来事件的选择器。默认 `EventKind::Message`。`None` 字段是该维度的通配,
28/// 所有 `Some` 字段必须全部命中(AND)。任意额外要求(含命令/文本匹配)走 `filter`。
29#[derive(Clone)]
30pub struct Selector {
31    pub kind: EventKind,
32    pub peer: Option<Peer>,
33    pub user: Option<Uin>,
34    pub filter: Option<EventFilter>,
35}
36
37impl Default for Selector {
38    fn default() -> Self {
39        Selector { kind: EventKind::Message, peer: None, user: None, filter: None }
40    }
41}
42
43impl Selector {
44    /// `event` 是否满足本选择器所有受约束的维度。
45    pub fn matches(&self, event: &Event) -> bool {
46        if EventKind::of(event) != Some(self.kind) {
47            return false;
48        }
49        if let Some(p) = self.peer {
50            if event.peer() != Some(p) {
51                return false;
52            }
53        }
54        if let Some(u) = self.user {
55            if event.sender() != Some(u) {
56                return false;
57            }
58        }
59        if let Some(f) = &self.filter {
60            if !f(event) {
61                return false;
62            }
63        }
64        true
65    }
66}
67
68/// 仅消息的便捷预设(事件型作用域手写 `.on(Kind).filter(..)`)。
69#[derive(Clone)]
70pub struct Scope(Selector);
71
72impl Scope {
73    /// `peer`(某个具体的群或好友会话)里的消息。
74    ///
75    /// 取名 `peer`(而非 `group`)是为了协调一致:[`Scope`] 寻址一个完整的 [`Peer`]
76    /// (带场景信息),而 `Bot::group(impl Into<Uin>)` 收的是裸群号。名字区分开,避免
77    /// `Scope::group(123)` 看起来该能编译、实际只接受 `Peer`。
78    pub fn peer(peer: Peer) -> Scope {
79        Scope(Selector { peer: Some(peer), ..Selector::default() })
80    }
81    /// 任意会话里来自 `user` 的消息。
82    pub fn user(user: Uin) -> Scope {
83        Scope(Selector { user: Some(user), ..Selector::default() })
84    }
85    /// `peer` 里来自 `user` 的消息。
86    pub fn from(peer: Peer, user: Uin) -> Scope {
87        Scope(Selector { peer: Some(peer), user: Some(user), ..Selector::default() })
88    }
89    /// 消费成内层 `Selector`。
90    pub fn into_selector(self) -> Selector {
91        self.0
92    }
93}
94
95/// `try_deliver` 探测的结果。
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub struct Delivery {
98    pub delivered: bool,
99    pub block: bool,
100}
101
102/// [`recv_with`](Waiter::recv_with) / [`recv_with_sync`](Waiter::recv_with_sync)
103/// 的闭包对每条投递事件返回的类型化裁决:闭包**逐事件**决定继续等、用某值收束、还是放弃。
104///
105/// 刻意**没有 `Consume(T)`** 变体:waiter 条目的 `block`(吞没还是放行)在注册时
106/// (builder 上的 `.block(true|false)`)就固定了,`try_deliver` 在闭包跑**之前**读它。
107/// 故闭包裁决物理上无法回溯改写已发出的 `Delivery`——传播是构建期的选择,不是流程里的决定。
108pub enum WaitFlow<T> {
109    /// 在剩余超时内继续等。
110    Continue,
111    /// 用 `Some(value)` 收束本次等待。命中事件的传播在 `build()` 时已定。
112    Done(T),
113    /// 立即放弃 → 等待返回 `None`。
114    Cancel,
115}
116
117/// [`recv_parse`](Waiter::recv_parse) / [`recv_text`](Waiter::recv_text) 一次追问的结局:拿到值、
118/// 用户主动取消、或超时没等到。取代裸 `Option`——把「取消」与「超时」**分开**返回,调用侧据此分别回话
119/// (取消该道别、超时该说没等到),二者不再都塌成 `None`(那曾让发「取消」错报成超时)。
120pub enum Replied<T> {
121    /// 收到并(解析)成功的值。
122    Got(T),
123    /// 命中取消判定,用户主动放弃。
124    Cancelled,
125    /// 到点没等到。
126    TimedOut,
127}
128
129/// store 里一条已注册的 waiter。
130struct WaiterEntry {
131    id: u64,
132    depth: u32,
133    selector: Selector,
134    block: bool,
135    tx: mpsc::Sender<Arc<Event>>,
136}
137
138/// 活跃 waiter 的共享注册表。用 `Vec`(而非单槽),故多个并发/嵌套作用域可共存。
139/// 注入 app 共享态;router 在 `run_handlers` 的 waiter 检查层查询它。
140pub struct WaiterStore {
141    waiters: Mutex<Vec<WaiterEntry>>,
142    next_id: AtomicU64,
143}
144
145impl Default for WaiterStore {
146    fn default() -> Self {
147        Self { waiters: Mutex::new(Vec::new()), next_id: AtomicU64::new(1) }
148    }
149}
150
151impl WaiterStore {
152    pub fn new() -> Self {
153        Self::default()
154    }
155
156    fn lock(&self) -> std::sync::MutexGuard<'_, Vec<WaiterEntry>> {
157        self.waiters.lock().unwrap_or_else(|e| e.into_inner())
158    }
159
160    /// 取一个新的 waiter id(`Waiter` 用它,使 `Drop` 能精确摘除自己)。
161    pub fn next_id(&self) -> u64 {
162        self.next_id.fetch_add(1, Ordering::Relaxed)
163    }
164
165    /// 注册一条 waiter 条目,返回其 id(生产路径走 `WaiterBuilder::build`,它预分配好 id)。
166    pub fn register(&self, depth: u32, selector: Selector, block: bool, tx: mpsc::Sender<Arc<Event>>) -> u64 {
167        let id = self.next_id();
168        self.register_with_id(id, depth, selector, block, tx);
169        id
170    }
171
172    /// 用调用方给定的 id 注册(使 `Waiter` 持同一 id 供 `Drop` 摘除)。
173    pub fn register_with_id(&self, id: u64, depth: u32, selector: Selector, block: bool, tx: mpsc::Sender<Arc<Event>>) {
174        self.lock().push(WaiterEntry { id, depth, selector, block, tx });
175    }
176
177    /// 摘除 id 为 `id` 的条目(幂等;由 `Waiter::drop` 调用)。
178    pub fn remove(&self, id: u64) {
179        self.lock().retain(|e| e.id != id);
180    }
181
182    /// 当前存活条目数。
183    pub fn len(&self) -> usize {
184        self.lock().len()
185    }
186
187    /// 是否没有存活条目。
188    pub fn is_empty(&self) -> bool {
189        self.lock().is_empty()
190    }
191
192    /// 尝试把 `event` 投递给最深的匹配 waiter。投给已关闭的接收端则摘除该条目、记为未命中。
193    pub fn try_deliver(&self, event: &Arc<Event>) -> Delivery {
194        loop {
195            // 取 depth 最高的匹配条目;同深则取 id 最大者(最新)。
196            let chosen = {
197                let guard = self.lock();
198                guard
199                    .iter()
200                    .filter(|e| e.selector.matches(event))
201                    .max_by_key(|e| (e.depth, e.id))
202                    .map(|e| (e.id, e.block, e.tx.clone()))
203            };
204            let Some((id, block, tx)) = chosen else {
205                return Delivery { delivered: false, block: false };
206            };
207            match tx.try_send(Arc::clone(event)) {
208                Ok(()) => return Delivery { delivered: true, block },
209                // 接收端已 drop → waiter 已死;摘除后重试下一个匹配。
210                Err(TrySendError::Closed(_)) => {
211                    self.remove(id);
212                    continue;
213                }
214                // 缓冲满但接收端仍在 → waiter 是个活跃 session、只是 handler 暂时落后。**不**摘除
215                // (那会静默丢数据);当作未命中处理。
216                Err(TrySendError::Full(_)) => return Delivery { delivered: false, block: false },
217            }
218        }
219    }
220}
221
222/// single-flight 作用域的可哈希身份,从 [`Selector`] 的受约束维度(kind + peer + user)投影而来。
223/// 选择器任意的 `filter` **不**进键——single-flight 作用域(`Scope::peer`/`user`/`from`)从不设
224/// filter,且闭包本身也无从哈希/比较身份。
225#[derive(Clone, PartialEq, Eq, Hash, Debug)]
226struct FlightKey {
227    kind: EventKind,
228    peer: Option<Peer>,
229    user: Option<Uin>,
230}
231
232impl FlightKey {
233    fn from_selector(sel: &Selector) -> Self {
234        FlightKey { kind: sel.kind, peer: sel.peer, user: sel.user }
235    }
236}
237
238/// 在飞 single-flight 作用域的共享注册表。一个装着当前持有的作用域键
239/// (kind + peer + user,从 [`Selector`] 投影)的 `HashSet`,由 `Mutex` 守护(std 内存存储、
240/// 无新依赖——与 [`CooldownStore`](crate::CooldownStore) 同形状)。`App::new` 把它与
241/// `WaiterStore` 并排注入共享态;[`Session::single_flight`] 据此抢占。
242pub struct FlightStore {
243    held: Mutex<HashSet<FlightKey>>,
244}
245
246impl Default for FlightStore {
247    fn default() -> Self {
248        Self { held: Mutex::new(HashSet::new()) }
249    }
250}
251
252impl FlightStore {
253    /// 新建空存储。
254    pub fn new() -> Self {
255        Self::default()
256    }
257
258    fn lock(&self) -> std::sync::MutexGuard<'_, HashSet<FlightKey>> {
259        self.held.lock().unwrap_or_else(|e| e.into_inner())
260    }
261
262    /// 当前在飞的作用域数。
263    pub fn len(&self) -> usize {
264        self.lock().len()
265    }
266
267    /// 当前是否没有作用域在飞。
268    pub fn is_empty(&self) -> bool {
269        self.lock().is_empty()
270    }
271
272    /// 尝试抢占 `selector` 的作用域。作用域空闲时返回 [`FlightGuard`](drop 时释放);
273    /// 已被持有则返回 `None`(第二个并发启动被拒)。
274    fn acquire(store: Arc<FlightStore>, selector: Selector) -> Option<FlightGuard> {
275        let key = FlightKey::from_selector(&selector);
276        {
277            let mut held = store.lock();
278            if !held.insert(key.clone()) {
279                return None; // 已在飞 → 拒绝第二个并发启动。
280            }
281        }
282        Some(FlightGuard { store, key })
283    }
284}
285
286/// 一个 RAII 的 single-flight 令牌。存活期间把其作用域标记为在飞,故同作用域上第二次
287/// [`Session::single_flight`] 返回 `None`。其 `Drop` 释放作用域,故被守护 handler 的**每条**
288/// 退出路径——`?` 提前返回、正常路径、panic——都无需手写清理即可释放该槽。沿用 [`Waiter`] 的
289/// drop-摘除纪律。
290pub struct FlightGuard {
291    store: Arc<FlightStore>,
292    key: FlightKey,
293}
294
295impl Drop for FlightGuard {
296    fn drop(&mut self) {
297        self.store.lock().remove(&self.key);
298    }
299}
300
301/// 嵌套深度标记,打在重建的 `Ctx` 上,使从中提取的 `Session` 比产出它的 waiter 深一层注册。
302#[derive(Clone, Copy, Debug)]
303pub struct WaiterDepth(pub u32);
304
305/// `Waiter` 的构建器。默认 `kind = Message`、`block = true`。`depth` 由创建它的 `Session`
306/// 设定(`self.depth + 1`)。
307pub struct WaiterBuilder {
308    bot: Bot,
309    state: Arc<StateMap>,
310    store: Arc<WaiterStore>,
311    depth: u32,
312    selector: Selector,
313    block: bool,
314    /// session 的源会话 peer(如有)与触发用户(如有),随构建器携带,
315    /// 使 [`from_starter`](Self::from_starter) 能直接作用域到「这个 peer + 这个 user」,
316    /// 无需调用方重新填写。
317    starter_peer: Option<Peer>,
318    starter_user: Option<Uin>,
319}
320
321impl WaiterBuilder {
322    /// 监听该事件种类(默认 `Message`)。
323    pub fn on(mut self, kind: EventKind) -> Self {
324        self.selector.kind = kind;
325        self
326    }
327    /// 用 `Scope` 预设整个替换选择器。
328    pub fn scope(mut self, scope: Scope) -> Self {
329        self.selector = scope.into_selector();
330        self
331    }
332    /// 限定该会话 peer。
333    pub fn peer(mut self, peer: Peer) -> Self {
334        self.selector.peer = Some(peer);
335        self
336    }
337    /// 限定该行为用户。
338    pub fn user(mut self, user: Uin) -> Self {
339        self.selector.user = Some(user);
340        self
341    }
342    /// 限定 `peer` 里的 `user`。
343    pub fn from(mut self, peer: Peer, user: Uin) -> Self {
344        self.selector.peer = Some(peer);
345        self.selector.user = Some(user);
346        self
347    }
348    /// 作用域到**触发本 session 的会话和用户**:session 的源 peer 加其触发用户。这把每个
349    /// 「问当初发问的同一个人」waiter 否则都要重复的 `if grp == group && mbr == member { .. }`
350    /// 手写守卫收掉。只有 session 确实缺某维度时才留作通配(如根于无 peer 事件的 session 无
351    /// `starter_user`)。
352    pub fn from_starter(mut self) -> Self {
353        self.selector.peer = self.starter_peer;
354        self.selector.user = self.starter_user;
355        self
356    }
357    /// 额外要求该任意谓词。
358    pub fn filter<F>(mut self, f: F) -> Self
359    where
360        F: Fn(&Event) -> bool + Send + Sync + 'static,
361    {
362        self.selector.filter = Some(Arc::new(f));
363        self
364    }
365    /// 命中是否吞掉、不下传到更低层(默认 `true`)。
366    pub fn block(mut self, block: bool) -> Self {
367        self.block = block;
368        self
369    }
370    /// 注册条目并返回 RAII 的 `Waiter`。
371    pub fn build(self) -> Waiter {
372        let (tx, rx) = mpsc::channel(16);
373        let id = self.store.next_id();
374        self.store.register_with_id(id, self.depth, self.selector, self.block, tx);
375        Waiter {
376            id,
377            rx: tokio::sync::Mutex::new(rx),
378            store: self.store,
379            bot: self.bot,
380            state: self.state,
381            depth: self.depth,
382        }
383    }
384}
385
386/// 一个活跃 waiter。RAII:其 `Drop` 摘除 store 条目。经 `mpsc` 收投递事件(多轮、无需重注册)。
387pub struct Waiter {
388    id: u64,
389    rx: tokio::sync::Mutex<mpsc::Receiver<Arc<Event>>>,
390    store: Arc<WaiterStore>,
391    bot: Bot,
392    state: Arc<StateMap>,
393    depth: u32,
394}
395
396impl Drop for Waiter {
397    fn drop(&mut self) {
398        self.store.remove(self.id);
399    }
400}
401
402impl Waiter {
403    /// 等下一条投递事件(最多 `timeout`),把它重建成新 `Ctx`(使 `FromContext` 提取器可用),
404    /// 并打上 `WaiterDepth(self.depth)` 标记,使从中提取的嵌套 `Session` 注册得更深。
405    pub async fn recv_event(&self, timeout: Duration) -> Option<Ctx> {
406        let ev = self.recv_raw(timeout).await?;
407        let ctx = Ctx::new(ev, self.bot.clone(), Arc::clone(&self.state));
408        ctx.insert_ext(WaiterDepth(self.depth));
409        Some(ctx)
410    }
411
412    /// 循环:`recv_event` → 跑 `T::from_context`;遇 `Reject::Skip` 在**剩余**超时内继续等;
413    /// `Ok(v) -> Some(v)`;超时或 `Reject::Error -> None`。
414    pub async fn recv<T: FromContext>(&self, timeout: Duration) -> Option<T> {
415        Some(self.recv_session::<T>(timeout).await?.0)
416    }
417
418    /// 同 [`recv`](Self::recv),但额外从同一收到的事件重建一个就绪、深一层的 [`Session`] 一并返回。
419    /// 这是嵌套 / 事件发起式中断的顺手入口:handler 可在返回的 `Session` 上继续挂下一个 waiter,
420    /// 无需泄漏裸 `Ctx`、也无需手搓 `Error`(即旧的 `recv_event` → `Session::from_context(&ctx)`)。
421    pub async fn recv_session<T: FromContext>(&self, timeout: Duration) -> Option<(T, Session)> {
422        let deadline = tokio::time::Instant::now() + timeout;
423        loop {
424            let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
425            if remaining.is_zero() {
426                return None;
427            }
428            let ctx = self.recv_event(remaining).await?;
429            match T::from_context(&ctx).await {
430                // Ctx 带着 `WaiterDepth(self.depth)`,故提取出的 Session 比本 waiter 深一层注册。
431                // 这里提取必成功(WaiterStore 一定在——本 waiter 就是它产出的)。
432                Ok(v) => match Session::from_context(&ctx).await {
433                    Ok(session) => return Some((v, session)),
434                    Err(_) => return None,
435                },
436                Err(Reject::Skip) => continue,
437                Err(Reject::Error(_)) => return None,
438            }
439        }
440    }
441
442    /// 类型化的逐事件裁决等待。闭包收到从每条投递事件重建的、**owned、可做依赖注入**的 `Ctx`
443    /// (故能提取任意东西——跑 `Slots<T>` 匹配、经 `Reply::from_context` 引用、对话中途回话),
444    /// 返回类型化的 [`WaitFlow<T>`]:
445    /// - [`WaitFlow::Continue`] —— 在**剩余**超时内继续等;
446    /// - [`WaitFlow::Done(v)`](WaitFlow::Done) —— 收束为 `Some(v)`;
447    /// - [`WaitFlow::Cancel`] —— 立即放弃 → `None`。
448    ///
449    /// 传播(吞没还是放行)**不是**闭包裁决:它在注册时经 builder 的 `.block(true|false)` 固定。
450    /// 闭包无法回溯吞掉刚命中它的事件——`try_deliver` 在本闭包跑之前就已发出 `build()` 时定下的
451    /// `Delivery.block`。
452    ///
453    /// ## 借用合约
454    ///
455    /// `Ctx` 不是 `Clone`,故每轮产出一个**全新 owned** 的 `Ctx`。由于 `FnMut` 返回的 `async move`
456    /// future *逃出*闭包体,这里无法把裸 `&mut` 借用跨 await 点持有;**async** 路径上要跨轮可变状态,
457    /// 就捕获一个共享 cell(`Rc<RefCell<_>>` / `Arc<Mutex<_>>`)并**每轮**克隆句柄,使每个 future
458    /// 持有自己的重借用:
459    ///
460    /// ```ignore
461    /// let players = Rc::new(RefCell::new(roster));      // FnMut 捕获的共享 cell
462    /// let outcome = waiter.recv_with(timeout, |ctx| {
463    ///     let players = Rc::clone(&players);            // 每轮重借用
464    ///     async move {                                  // 只 move 这个克隆 + owned 的 `ctx`
465    ///         // players.borrow_mut().push(..); 经 Reply::from_context(&ctx) 回话 …
466    ///     }
467    /// }).await;
468    /// ```
469    ///
470    /// 裁决**无需**等待中途 `.await` 时,优先用 [`recv_with_sync`](Self::recv_with_sync):其闭包直接
471    /// 返回 `WaitFlow<T>`,跨轮的裸 `&mut` 无 async-move 税即可用(常见的对战/开局凑人形态)。
472    pub async fn recv_with<T, F, Fut>(&self, timeout: Duration, mut f: F) -> Option<T>
473    where
474        F: FnMut(Ctx) -> Fut,
475        Fut: Future<Output = WaitFlow<T>>,
476    {
477        let deadline = Instant::now() + timeout;
478        loop {
479            let remaining = deadline.saturating_duration_since(Instant::now());
480            if remaining.is_zero() {
481                return None;
482            }
483            let ctx = self.recv_event(remaining).await?;
484            match f(ctx).await {
485                WaitFlow::Continue => continue,
486                WaitFlow::Done(v) => return Some(v),
487                WaitFlow::Cancel => return None,
488            }
489        }
490    }
491
492    /// [`recv_with`](Self::recv_with) 的**同步**裁决变体:闭包**无中途 `.await`** 地直接返回
493    /// [`WaitFlow<T>`],故捕获的 `&mut` 状态无需 async-move 重借用税。这是常见的「命中或继续等、
494    /// 裁决里不做 I/O」形态;裁决本身须 `.await`(查 DB、等待中途回话)时用
495    /// [`recv_with`](Self::recv_with)。
496    pub async fn recv_with_sync<T, F>(&self, timeout: Duration, mut f: F) -> Option<T>
497    where
498        F: FnMut(Ctx) -> WaitFlow<T>,
499    {
500        let deadline = Instant::now() + timeout;
501        loop {
502            let remaining = deadline.saturating_duration_since(Instant::now());
503            if remaining.is_zero() {
504                return None;
505            }
506            let ctx = self.recv_event(remaining).await?;
507            match f(ctx) {
508                WaitFlow::Continue => continue,
509                WaitFlow::Done(v) => return Some(v),
510                WaitFlow::Cancel => return None,
511            }
512        }
513    }
514
515    /// 是/否确认。等 `who` 的一条消息;`yes` 词收束 `Some(true)`,`no` 词收束 `Some(false)`。
516    /// **其它任何输入都重新追问**(回贴 `yes`/`no` 提示、在剩余超时内继续等)而非收束,
517    /// 即「没听懂,请回答 y/n」式的循环。超时 → `None`。
518    ///
519    /// 匹配在消息纯文本上做:去首尾空白、大小写不敏感。
520    pub async fn confirm(&self, timeout: Duration, who: Uin, yes: &str, no: &str) -> Option<bool> {
521        let yes = yes.trim().to_lowercase();
522        let no = no.trim().to_lowercase();
523        self.recv_with(timeout, |ctx| {
524            let yes = yes.clone();
525            let no = no.clone();
526            async move {
527                let Some(m) = ctx.message() else { return WaitFlow::Continue };
528                if m.sender != who {
529                    return WaitFlow::Continue;
530                }
531                let text = m.content.extract_text();
532                let t = text.trim().to_lowercase();
533                if t == yes {
534                    WaitFlow::Done(true)
535                } else if t == no {
536                    WaitFlow::Done(false)
537                } else {
538                    // 来自目标用户的无法识别输入 → 重新追问、继续等。
539                    if let Ok(reply) = Reply::from_context(&ctx).await {
540                        let _ = reply.text(format!("请回复「{yes}」或「{no}」")).await;
541                    }
542                    WaitFlow::Continue
543                }
544            }
545        })
546        .await
547    }
548
549    /// 等一行自由文本。作用域内首条非空消息收束 [`Replied::Got`];`cancel` 谓词命中的消息 →
550    /// [`Replied::Cancelled`];空/纯空白文本忽略(继续等);超时 → [`Replied::TimedOut`]。
551    /// `cancel` 是谓词而非单个词面——多词面取消(取消/算了/退出…)由调用方一处定、各处共用。
552    pub async fn recv_text(&self, timeout: Duration, cancel: impl Fn(&str) -> bool) -> Replied<String> {
553        self.recv_with_sync(timeout, |ctx| {
554            let Some(m) = ctx.message() else { return WaitFlow::Continue };
555            let text = m.content.extract_text();
556            let trimmed = text.trim();
557            if trimmed.is_empty() {
558                return WaitFlow::Continue;
559            }
560            if cancel(trimmed) {
561                return WaitFlow::Done(Replied::Cancelled);
562            }
563            WaitFlow::Done(Replied::Got(text))
564        })
565        .await
566        .unwrap_or(Replied::TimedOut)
567    }
568
569    /// 等一行自由文本并**解析它、失败则重新追问**([`confirm`](Self::confirm)/
570    /// [`recv_text`](Self::recv_text) 的兄弟)。`parse` 把 trim 后的文本映射成 `Ok(value)` →
571    /// [`Replied::Got`],或 `Err(hint)` → 回贴 `hint` 并在剩余超时内继续等。`cancel` 谓词命中 →
572    /// [`Replied::Cancelled`];空/空白输入忽略;超时 → [`Replied::TimedOut`]。
573    ///
574    /// 与兄弟方法一样,它在**内部**派生 [`Reply`](无 `&Reply` 形参)并吞掉发送错误,故一个多步追问
575    /// 收成一次调用。**取消与超时分开返回**,调用侧据此分别回话(取消道别 / 超时提示),不再都塌成
576    /// 裸 `None`。`cancel` 是谓词——多词面取消由调用方一处定、各处共用。
577    pub async fn recv_parse<T, F, C>(&self, timeout: Duration, cancel: C, parse: F) -> Replied<T>
578    where
579        F: Fn(&str) -> std::result::Result<T, String>,
580        C: Fn(&str) -> bool,
581    {
582        // 局部两段式裁决:先同步解析(借 `ctx`),再仅为重新追问把 `ctx` move 进 async 块——
583        // 让 `parse`/`cancel`(都是 `Fn`)留在被 move 的 future 之外。
584        enum Pre<U> {
585            Skip,
586            Cancel,
587            Done(U),
588            Reprompt(String),
589        }
590        self.recv_with(timeout, move |ctx| {
591            let pre = match ctx.message() {
592                None => Pre::Skip,
593                Some(m) => {
594                    let text = m.content.extract_text();
595                    let trimmed = text.trim();
596                    if trimmed.is_empty() {
597                        Pre::Skip
598                    } else if cancel(trimmed) {
599                        Pre::Cancel
600                    } else {
601                        match parse(trimmed) {
602                            Ok(v) => Pre::Done(v),
603                            Err(hint) => Pre::Reprompt(hint),
604                        }
605                    }
606                }
607            };
608            async move {
609                match pre {
610                    Pre::Skip => WaitFlow::Continue,
611                    Pre::Cancel => WaitFlow::Done(Replied::Cancelled),
612                    Pre::Done(v) => WaitFlow::Done(Replied::Got(v)),
613                    Pre::Reprompt(hint) => {
614                        if let Ok(reply) = Reply::from_context(&ctx).await {
615                            let _ = reply.text(hint).await;
616                        }
617                        WaitFlow::Continue
618                    }
619                }
620            }
621        })
622        .await
623        .unwrap_or(Replied::TimedOut)
624    }
625
626    async fn rx_guard(&self) -> tokio::sync::MutexGuard<'_, mpsc::Receiver<Arc<Event>>> {
627        self.rx.lock().await
628    }
629
630    async fn recv_raw(&self, timeout: Duration) -> Option<Arc<Event>> {
631        let mut rx = self.rx_guard().await;
632        match tokio::time::timeout(timeout, rx.recv()).await {
633            Ok(Some(ev)) => Some(ev),
634            _ => None,
635        }
636    }
637}
638
639/// 一个会话句柄(`FromContext` 提取器)。捕获 bot、共享态、`WaiterStore`(取自共享态)、
640/// 当前会话 peer(如有),以及当前嵌套深度(取自 Ctx 上的 `WaiterDepth` ext,默认 0)。
641pub struct Session {
642    bot: Bot,
643    state: Arc<StateMap>,
644    store: Arc<WaiterStore>,
645    flight: Arc<FlightStore>,
646    peer: Option<Peer>,
647    user: Option<Uin>,
648    depth: u32,
649}
650
651impl Session {
652    /// 提取本 session 的会话 peer(如有)。根于无 peer 事件的 session 为 `None`。
653    pub fn peer(&self) -> Option<Peer> {
654        self.peer
655    }
656
657    /// 触发本 session 的用户(如有,即消息发送者)。根于无 sender 事件的 session 为 `None`。
658    pub fn user(&self) -> Option<Uin> {
659        self.user
660    }
661
662    /// 开一个比本 session 深一层的 waiter。
663    pub fn waiter(&self) -> WaiterBuilder {
664        WaiterBuilder {
665            bot: self.bot.clone(),
666            state: Arc::clone(&self.state),
667            store: Arc::clone(&self.store),
668            depth: self.depth + 1,
669            selector: Selector::default(),
670            block: true,
671            starter_peer: self.peer,
672            starter_user: self.user,
673        }
674    }
675
676    /// 在 `scope` 上抢一个 single-flight 守卫。若另一次调用已持同一作用域则返回 `None`
677    /// (第二个并发启动被拒)。返回的 [`FlightGuard`] drop 时自动释放,故每条退出路径——含 `?`
678    /// 提前返回与 panic——都无需手写 `del RUNNING[..]` 即可释放该槽。
679    ///
680    /// 语法糖:`Scope::user(uin)` 按人、`Scope::peer(peer)` 按会话(典型的游戏守卫)、
681    /// `Scope::from(peer, uin)` 按「会话内某人」。
682    pub fn single_flight(&self, scope: Scope) -> Option<FlightGuard> {
683        FlightStore::acquire(Arc::clone(&self.flight), scope.into_selector())
684    }
685
686    /// 作用域到**本 session 自己的触发用户**的 single-flight——是 waiter 的
687    /// [`WaiterBuilder::from_starter`] 在 flight 上的对应物,故常见的「行为用户」守卫无需
688    /// `Scope::user(Uin(me))` 绕一圈。若该用户已持有作用域、**或** session 无触发用户(无 peer
689    /// 事件)则返回 `None`——对 `#[command]`(消息)handler 无害,它们总有 sender。
690    pub fn single_flight_user(&self) -> Option<FlightGuard> {
691        self.user.and_then(|u| self.single_flight(Scope::user(u)))
692    }
693}
694
695#[async_trait]
696impl FromContext for Session {
697    async fn from_context(ctx: &Ctx) -> Extracted<Self> {
698        let store = ctx
699            .state()
700            .get(&TypeId::of::<WaiterStore>())
701            .and_then(|a| Arc::clone(a).downcast::<WaiterStore>().ok())
702            .ok_or_else(|| {
703                Reject::Error(Error::action_kind(
704                    ActionErrorKind::Other,
705                    "WaiterStore not registered (use App::new or register it)",
706                ))
707            })?;
708        // FlightStore 由 `App::new` 与 WaiterStore 并排自动注册。若是裸 Router 没装它(嵌入方场景),
709        // 退回一个 per-session 的新 store,使 `single_flight` 仍可用(只是此时它不跨 session 共享)。
710        let flight = ctx
711            .state()
712            .get(&TypeId::of::<FlightStore>())
713            .and_then(|a| Arc::clone(a).downcast::<FlightStore>().ok())
714            .unwrap_or_else(|| Arc::new(FlightStore::new()));
715        let depth = ctx.get_ext::<WaiterDepth>().map(|d| d.0).unwrap_or(0);
716        Ok(Session {
717            bot: ctx.bot().clone(),
718            state: Arc::clone(ctx.state()),
719            store,
720            flight,
721            peer: ctx.event().peer(),
722            user: ctx.event().sender(),
723            depth,
724        })
725    }
726}