use crate::bot::Bot;
use crate::ctx::{Ctx, StateMap};
use crate::matcher::ParsedCommand;
use async_trait::async_trait;
use nagisa_types::event::{MessageEvent, Meta, Notice, Request};
use nagisa_types::prelude::*;
use nagisa_types::segment::Segment;
use std::any::{type_name, TypeId};
use std::sync::Arc;
#[derive(Debug)]
pub enum Reject {
Skip,
Error(Error),
}
pub type Extracted<T> = std::result::Result<T, Reject>;
impl From<Reject> for Error {
fn from(r: Reject) -> Self {
match r {
Reject::Error(e) => e,
Reject::Skip => Error::action_kind(
ActionErrorKind::BadParams,
"handler skipped (Reject::Skip propagated via `?`, e.g. cooldown hit)",
),
}
}
}
#[async_trait]
pub trait FromContext: Sized {
async fn from_context(ctx: &Ctx) -> Extracted<Self>;
}
#[async_trait]
impl FromContext for Bot {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
Ok(ctx.bot().clone())
}
}
#[async_trait]
impl<T: FromContext + Send> FromContext for Option<T> {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
match T::from_context(ctx).await {
Ok(v) => Ok(Some(v)),
Err(Reject::Skip) => Ok(None),
Err(Reject::Error(e)) => Err(Reject::Error(e)),
}
}
}
#[async_trait]
impl FromContext for Arc<Event> {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
Ok(Arc::clone(ctx.event()))
}
}
#[async_trait]
impl FromContext for MessageEvent {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
ctx.message().cloned().ok_or(Reject::Skip)
}
}
pub struct GroupMessage(pub MessageEvent);
#[async_trait]
impl FromContext for GroupMessage {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
match ctx.message() {
Some(m) if m.peer.scene == Scene::Group => Ok(GroupMessage(m.clone())),
_ => Err(Reject::Skip),
}
}
}
pub struct PrivateMessage(pub MessageEvent);
#[async_trait]
impl FromContext for PrivateMessage {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
match ctx.message() {
Some(m) if matches!(m.peer.scene, Scene::Friend | Scene::Temp) => {
Ok(PrivateMessage(m.clone()))
}
_ => Err(Reject::Skip),
}
}
}
pub struct Sender(pub Uin);
#[async_trait]
impl FromContext for Sender {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
ctx.message().map(|m| Sender(m.sender)).ok_or(Reject::Skip)
}
}
pub struct EventPeer(pub Peer);
#[async_trait]
impl FromContext for EventPeer {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
ctx.message().map(|m| EventPeer(m.peer)).ok_or(Reject::Skip)
}
}
#[async_trait]
impl FromContext for nagisa_types::id::Scene {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
ctx.message().map(|m| m.peer.scene).ok_or(Reject::Skip)
}
}
pub struct State<T: Send + Sync + 'static>(pub Arc<T>);
impl<T: Send + Sync + 'static> std::ops::Deref for State<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
#[async_trait]
impl<T: Send + Sync + 'static> FromContext for State<T> {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
let state: &StateMap = ctx.state();
match state.get(&TypeId::of::<T>()) {
Some(any) => match Arc::clone(any).downcast::<T>() {
Ok(typed) => Ok(State(typed)),
Err(_) => Err(Reject::Error(Error::action(format!(
"state type mismatch for {}",
type_name::<T>()
)))),
},
None => Err(Reject::Error(Error::action_kind(
ActionErrorKind::BadParams,
format!("missing app state `{}` (register via Router::data)", type_name::<T>()),
))),
}
}
}
#[async_trait]
impl FromContext for Notice {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
match ctx.event().as_ref() {
Event::Notice(n) => Ok(n.clone()),
_ => Err(Reject::Skip),
}
}
}
#[async_trait]
impl FromContext for Request {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
match ctx.event().as_ref() {
Event::Request(r) => Ok(r.clone()),
_ => Err(Reject::Skip),
}
}
}
#[async_trait]
impl FromContext for Meta {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
match ctx.event().as_ref() {
Event::Meta(m) => Ok(m.clone()),
_ => Err(Reject::Skip),
}
}
}
pub struct Reply {
peer: Peer,
trigger: MessageId,
bot: Bot,
}
impl Reply {
pub async fn text(&self, s: impl Into<String>) -> Result<MessageId> {
self.bot.send(&self.peer, &[Segment::text(s)]).await
}
pub async fn send(&self, segs: &[Segment]) -> Result<MessageId> {
self.bot.send(&self.peer, segs).await
}
pub async fn quote(&self, segs: &[Segment]) -> Result<MessageId> {
let mut out = Vec::with_capacity(segs.len() + 1);
out.push(Segment::reply(self.trigger.clone()));
out.extend_from_slice(segs);
self.bot.send(&self.peer, &out).await
}
pub async fn reply(&self, s: impl Into<String>) -> Result<MessageId> {
self.quote(&[Segment::text(s)]).await
}
pub async fn image(&self, url: impl Into<String>) -> Result<MessageId> {
self.bot.send(&self.peer, &[Segment::image_url(url)]).await
}
pub async fn at(&self, user: impl Into<Uin>) -> Result<MessageId> {
self.bot.send(&self.peer, &[Segment::at(user)]).await
}
pub async fn face(&self, id: impl Into<String>) -> Result<MessageId> {
self.bot.send(&self.peer, &[Segment::face(id)]).await
}
pub fn msg(&self) -> ReplyMsg<'_> {
ReplyMsg { reply: self, msg: Msg::new() }
}
pub fn peer(&self) -> &Peer {
&self.peer
}
pub fn trigger(&self) -> &MessageId {
&self.trigger
}
}
pub struct ReplyMsg<'a> {
reply: &'a Reply,
msg: Msg,
}
impl ReplyMsg<'_> {
pub fn text(mut self, s: impl Into<String>) -> Self {
self.msg = self.msg.text(s);
self
}
pub fn at(mut self, user: impl Into<Uin>) -> Self {
self.msg = self.msg.at(user);
self
}
pub fn at_all(mut self) -> Self {
self.msg = self.msg.at_all();
self
}
pub fn face(mut self, id: impl Into<String>) -> Self {
self.msg = self.msg.face(id);
self
}
pub fn image_url(mut self, url: impl Into<String>) -> Self {
self.msg = self.msg.image_url(url);
self
}
pub fn image_bytes(mut self, bytes: impl Into<bytes::Bytes>) -> Self {
self.msg = self.msg.image_bytes(bytes);
self
}
pub fn image_path(mut self, path: impl Into<std::path::PathBuf>) -> Self {
self.msg = self.msg.image_path(path);
self
}
pub fn reply_to_trigger(mut self) -> Self {
self.msg = self.msg.reply(self.reply.trigger.clone());
self
}
pub fn push(mut self, seg: Segment) -> Self {
self.msg = self.msg.push(seg);
self
}
pub async fn send(self) -> Result<MessageId> {
self.reply.send(&self.msg.build()).await
}
pub async fn quote(self) -> Result<MessageId> {
self.reply.quote(&self.msg.build()).await
}
}
#[async_trait]
impl FromContext for Reply {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
match ctx.message() {
Some(m) => Ok(Reply { peer: m.peer, trigger: m.id.clone(), bot: ctx.bot().clone() }),
None => Err(Reject::Skip),
}
}
}
pub struct CommandArg(pub Vec<Segment>);
#[async_trait]
impl FromContext for CommandArg {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
ctx.get_ext::<ParsedCommand>().map(|p| CommandArg(p.args)).ok_or(Reject::Skip)
}
}
pub struct ArgText(pub String);
#[async_trait]
impl FromContext for ArgText {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
ctx.get_ext::<ParsedCommand>().map(|p| ArgText(p.args_text)).ok_or(Reject::Skip)
}
}
pub struct Command(pub String);
#[async_trait]
impl FromContext for Command {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
ctx.get_ext::<ParsedCommand>().map(|p| Command(p.command)).ok_or(Reject::Skip)
}
}
pub struct Captures(pub Vec<String>);
#[async_trait]
impl FromContext for Captures {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
ctx.get_ext::<ParsedCommand>().map(|p| Captures(p.captures)).ok_or(Reject::Skip)
}
}
fn first_in_chain<R>(ctx: &Ctx, f: impl Fn(&Segment) -> Option<R>) -> Option<R> {
if let Some(p) = ctx.get_ext::<ParsedCommand>() {
return p.args.iter().find_map(&f);
}
ctx.message()?.content.iter().find_map(&f)
}
pub struct At(pub Uin);
#[async_trait]
impl FromContext for At {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
first_in_chain(ctx, crate::args::seg_as_at).map(At).ok_or(Reject::Skip)
}
}
pub struct Image(pub Media);
#[async_trait]
impl FromContext for Image {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
first_in_chain(ctx, crate::args::seg_as_image).map(Image).ok_or(Reject::Skip)
}
}
pub struct ToMe(pub bool);
#[async_trait]
impl FromContext for ToMe {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
let msg = ctx.message().ok_or(Reject::Skip)?;
if matches!(msg.peer.scene, Scene::Friend | Scene::Temp) {
return Ok(ToMe(true));
}
let self_id = ctx.bot().self_id();
let to_me = msg.content.first().is_some_and(|seg| {
matches!(seg, Segment::Mention { user, .. } if *user == self_id)
});
Ok(ToMe(to_me))
}
}