1use crate::bot::Bot;
6use crate::ctx::{Ctx, StateMap};
7use crate::matcher::ParsedCommand;
8use async_trait::async_trait;
9use nagisa_types::event::{MessageEvent, Meta, Notice, Request};
10use nagisa_types::prelude::*;
11use nagisa_types::segment::Segment;
12use std::any::{type_name, TypeId};
13use std::sync::Arc;
14
15#[derive(Debug)]
17pub enum Reject {
18 Skip,
20 Error(Error),
22}
23
24pub type Extracted<T> = std::result::Result<T, Reject>;
26
27impl From<Reject> for Error {
43 fn from(r: Reject) -> Self {
44 match r {
45 Reject::Error(e) => e,
46 Reject::Skip => Error::action_kind(
47 ActionErrorKind::BadParams,
48 "handler skipped (Reject::Skip propagated via `?`, e.g. cooldown hit)",
49 ),
50 }
51 }
52}
53
54#[async_trait]
56pub trait FromContext: Sized {
57 async fn from_context(ctx: &Ctx) -> Extracted<Self>;
58}
59
60#[async_trait]
61impl FromContext for Bot {
62 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
63 Ok(ctx.bot().clone())
64 }
65}
66
67#[async_trait]
70impl<T: FromContext + Send> FromContext for Option<T> {
71 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
72 match T::from_context(ctx).await {
73 Ok(v) => Ok(Some(v)),
74 Err(Reject::Skip) => Ok(None),
75 Err(Reject::Error(e)) => Err(Reject::Error(e)),
76 }
77 }
78}
79
80#[async_trait]
81impl FromContext for Arc<Event> {
82 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
83 Ok(Arc::clone(ctx.event()))
84 }
85}
86
87#[async_trait]
88impl FromContext for MessageEvent {
89 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
91 ctx.message().cloned().ok_or(Reject::Skip)
92 }
93}
94
95pub struct GroupMessage(pub MessageEvent);
97
98#[async_trait]
99impl FromContext for GroupMessage {
100 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
101 match ctx.message() {
102 Some(m) if m.peer.scene == Scene::Group => Ok(GroupMessage(m.clone())),
103 _ => Err(Reject::Skip),
104 }
105 }
106}
107
108pub struct PrivateMessage(pub MessageEvent);
110
111#[async_trait]
112impl FromContext for PrivateMessage {
113 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
114 match ctx.message() {
115 Some(m) if matches!(m.peer.scene, Scene::Friend | Scene::Temp) => Ok(PrivateMessage(m.clone())),
116 _ => Err(Reject::Skip),
117 }
118 }
119}
120
121pub struct Sender(pub Uin);
123
124#[async_trait]
125impl FromContext for Sender {
126 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
127 ctx.message().map(|m| Sender(m.sender)).ok_or(Reject::Skip)
128 }
129}
130
131pub struct EventPeer(pub Peer);
133
134#[async_trait]
135impl FromContext for EventPeer {
136 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
137 ctx.message().map(|m| EventPeer(m.peer)).ok_or(Reject::Skip)
138 }
139}
140
141#[async_trait]
150impl FromContext for nagisa_types::id::Scene {
151 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
152 ctx.message().map(|m| m.peer.scene).ok_or(Reject::Skip)
154 }
155}
156
157pub struct State<T: Send + Sync + 'static>(pub Arc<T>);
162
163impl<T: Send + Sync + 'static> std::ops::Deref for State<T> {
164 type Target = T;
165 fn deref(&self) -> &T {
166 &self.0
167 }
168}
169
170#[async_trait]
171impl<T: Send + Sync + 'static> FromContext for State<T> {
172 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
173 let state: &StateMap = ctx.state();
174 match state.get(&TypeId::of::<T>()) {
175 Some(any) => match Arc::clone(any).downcast::<T>() {
176 Ok(typed) => Ok(State(typed)),
177 Err(_) => Err(Reject::Error(Error::action(format!("state type mismatch for {}", type_name::<T>())))),
179 },
180 None => Err(Reject::Error(Error::action_kind(
181 ActionErrorKind::BadParams,
182 format!("missing app state `{}` (register via Router::data)", type_name::<T>()),
183 ))),
184 }
185 }
186}
187
188#[async_trait]
190impl FromContext for Notice {
191 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
192 match ctx.event().as_ref() {
193 Event::Notice(n) => Ok(n.clone()),
194 _ => Err(Reject::Skip),
195 }
196 }
197}
198
199#[async_trait]
201impl FromContext for Request {
202 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
203 match ctx.event().as_ref() {
204 Event::Request(r) => Ok(r.clone()),
205 _ => Err(Reject::Skip),
206 }
207 }
208}
209
210#[async_trait]
212impl FromContext for Meta {
213 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
214 match ctx.event().as_ref() {
215 Event::Meta(m) => Ok(m.clone()),
216 _ => Err(Reject::Skip),
217 }
218 }
219}
220
221pub struct Reply {
224 peer: Peer,
225 trigger: MessageId,
228 bot: Bot,
229}
230
231impl Reply {
232 pub async fn text(&self, s: impl Into<String>) -> Result<MessageId> {
234 self.bot.send(&self.peer, &[Segment::text(s)]).await
235 }
236
237 pub async fn send(&self, segs: &[Segment]) -> Result<MessageId> {
239 self.bot.send(&self.peer, segs).await
240 }
241
242 pub async fn quote(&self, segs: &[Segment]) -> Result<MessageId> {
244 let mut out = Vec::with_capacity(segs.len() + 1);
245 out.push(Segment::reply(self.trigger.clone()));
246 out.extend_from_slice(segs);
247 self.bot.send(&self.peer, &out).await
248 }
249
250 pub async fn reply(&self, s: impl Into<String>) -> Result<MessageId> {
252 self.quote(&[Segment::text(s)]).await
253 }
254
255 pub async fn image(&self, url: impl Into<String>) -> Result<MessageId> {
257 self.bot.send(&self.peer, &[Segment::image_url(url)]).await
258 }
259
260 pub async fn at(&self, user: impl Into<Uin>) -> Result<MessageId> {
262 self.bot.send(&self.peer, &[Segment::at(user)]).await
263 }
264
265 pub async fn face(&self, id: impl Into<String>) -> Result<MessageId> {
267 self.bot.send(&self.peer, &[Segment::face(id)]).await
268 }
269
270 pub fn msg(&self) -> ReplyMsg<'_> {
274 ReplyMsg { reply: self, msg: Msg::new() }
275 }
276
277 pub fn peer(&self) -> &Peer {
279 &self.peer
280 }
281
282 pub fn trigger(&self) -> &MessageId {
284 &self.trigger
285 }
286}
287
288pub struct ReplyMsg<'a> {
292 reply: &'a Reply,
293 msg: Msg,
294}
295
296impl ReplyMsg<'_> {
297 pub fn text(mut self, s: impl Into<String>) -> Self {
298 self.msg = self.msg.text(s);
299 self
300 }
301 pub fn at(mut self, user: impl Into<Uin>) -> Self {
302 self.msg = self.msg.at(user);
303 self
304 }
305 pub fn at_all(mut self) -> Self {
306 self.msg = self.msg.at_all();
307 self
308 }
309 pub fn face(mut self, id: impl Into<String>) -> Self {
310 self.msg = self.msg.face(id);
311 self
312 }
313 pub fn image_url(mut self, url: impl Into<String>) -> Self {
314 self.msg = self.msg.image_url(url);
315 self
316 }
317 pub fn image_bytes(mut self, bytes: impl Into<bytes::Bytes>) -> Self {
320 self.msg = self.msg.image_bytes(bytes);
321 self
322 }
323 pub fn image_path(mut self, path: impl Into<std::path::PathBuf>) -> Self {
325 self.msg = self.msg.image_path(path);
326 self
327 }
328 pub fn reply_to_trigger(mut self) -> Self {
330 self.msg = self.msg.reply(self.reply.trigger.clone());
331 self
332 }
333 pub fn push(mut self, seg: Segment) -> Self {
335 self.msg = self.msg.push(seg);
336 self
337 }
338 pub async fn send(self) -> Result<MessageId> {
340 self.reply.send(&self.msg.build()).await
341 }
342 pub async fn quote(self) -> Result<MessageId> {
344 self.reply.quote(&self.msg.build()).await
345 }
346}
347
348#[async_trait]
349impl FromContext for Reply {
350 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
351 match ctx.message() {
352 Some(m) => Ok(Reply { peer: m.peer, trigger: m.id.clone(), bot: ctx.bot().clone() }),
353 None => Err(Reject::Skip),
354 }
355 }
356}
357
358pub struct CommandArg(pub Vec<Segment>);
364
365#[async_trait]
366impl FromContext for CommandArg {
367 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
368 ctx.get_ext::<ParsedCommand>().map(|p| CommandArg(p.args)).ok_or(Reject::Skip)
369 }
370}
371
372pub struct ArgText(pub String);
374
375#[async_trait]
376impl FromContext for ArgText {
377 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
378 ctx.get_ext::<ParsedCommand>().map(|p| ArgText(p.args_text)).ok_or(Reject::Skip)
379 }
380}
381
382pub struct Command(pub String);
384
385#[async_trait]
386impl FromContext for Command {
387 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
388 ctx.get_ext::<ParsedCommand>().map(|p| Command(p.command)).ok_or(Reject::Skip)
389 }
390}
391
392pub struct Captures(pub Vec<String>);
394
395#[async_trait]
396impl FromContext for Captures {
397 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
398 ctx.get_ext::<ParsedCommand>().map(|p| Captures(p.captures)).ok_or(Reject::Skip)
399 }
400}
401
402fn first_in_chain<R>(ctx: &Ctx, f: impl Fn(&Segment) -> Option<R>) -> Option<R> {
420 if let Some(p) = ctx.get_ext::<ParsedCommand>() {
421 return p.args.iter().find_map(&f);
422 }
423 ctx.message()?.content.iter().find_map(&f)
424}
425
426pub struct At(pub Uin);
430
431#[async_trait]
432impl FromContext for At {
433 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
434 first_in_chain(ctx, crate::args::seg_as_at).map(At).ok_or(Reject::Skip)
435 }
436}
437
438pub struct Image(pub Media);
444
445#[async_trait]
446impl FromContext for Image {
447 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
448 first_in_chain(ctx, crate::args::seg_as_image).map(Image).ok_or(Reject::Skip)
449 }
450}
451
452pub struct ToMe(pub bool);
459
460#[async_trait]
461impl FromContext for ToMe {
462 async fn from_context(ctx: &Ctx) -> Extracted<Self> {
463 let msg = ctx.message().ok_or(Reject::Skip)?;
464 if matches!(msg.peer.scene, Scene::Friend | Scene::Temp) {
466 return Ok(ToMe(true));
467 }
468 let self_id = ctx.bot().self_id();
470 let to_me =
471 msg.content.first().is_some_and(|seg| matches!(seg, Segment::Mention { user, .. } if *user == self_id));
472 Ok(ToMe(to_me))
473 }
474}