use crate::ctx::Ctx;
use crate::plugin::SwitchKey;
use async_trait::async_trait;
use nagisa_types::entity::Role;
use nagisa_types::message::MessageExt;
use nagisa_types::prelude::*;
use nagisa_types::segment::Segment;
use std::any::TypeId;
use std::collections::HashSet;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
#[async_trait]
pub trait Check: Send + Sync + 'static {
async fn check(&self, ctx: &Ctx) -> bool;
}
#[derive(Clone)]
pub struct Rule(Arc<dyn Check>);
impl Rule {
pub fn new<C: Check>(c: C) -> Self {
Rule(Arc::new(c))
}
pub fn pred<F>(f: F) -> Self
where
F: Fn(&Ctx) -> bool + Send + Sync + 'static,
{
Rule::new(Pred(f))
}
pub async fn eval(&self, ctx: &Ctx) -> bool {
self.0.check(ctx).await
}
}
struct Pred<F>(F);
#[async_trait]
impl<F> Check for Pred<F>
where
F: Fn(&Ctx) -> bool + Send + Sync + 'static,
{
async fn check(&self, ctx: &Ctx) -> bool {
(self.0)(ctx)
}
}
struct And(Rule, Rule);
#[async_trait]
impl Check for And {
async fn check(&self, ctx: &Ctx) -> bool {
self.0.eval(ctx).await && self.1.eval(ctx).await
}
}
struct Or(Rule, Rule);
#[async_trait]
impl Check for Or {
async fn check(&self, ctx: &Ctx) -> bool {
self.0.eval(ctx).await || self.1.eval(ctx).await
}
}
struct Not(Rule);
#[async_trait]
impl Check for Not {
async fn check(&self, ctx: &Ctx) -> bool {
!self.0.eval(ctx).await
}
}
impl std::ops::BitAnd for Rule {
type Output = Rule;
fn bitand(self, rhs: Rule) -> Rule {
Rule::new(And(self, rhs))
}
}
impl std::ops::BitOr for Rule {
type Output = Rule;
fn bitor(self, rhs: Rule) -> Rule {
Rule::new(Or(self, rhs))
}
}
impl std::ops::Not for Rule {
type Output = Rule;
fn not(self) -> Rule {
Rule::new(Not(self))
}
}
pub fn to_me() -> Rule {
Rule::pred(|ctx| {
let Some(m) = ctx.message() else { return false };
if matches!(m.peer.scene, Scene::Friend | Scene::Temp) {
return true;
}
m.content.mentions_user(ctx.bot().self_id())
})
}
pub fn group_only() -> Rule {
Rule::pred(|ctx| ctx.message().map(|m| m.peer.scene == Scene::Group).unwrap_or(false))
}
pub fn private() -> Rule {
Rule::pred(|ctx| ctx.message().map(|m| matches!(m.peer.scene, Scene::Friend | Scene::Temp)).unwrap_or(false))
}
pub fn in_group(group: impl Into<Uin>) -> Rule {
let g = group.into();
Rule::pred(move |ctx| ctx.message().map(|m| m.peer.scene == Scene::Group && m.peer.id == g).unwrap_or(false))
}
pub fn from_user(user: impl Into<Uin>) -> Rule {
let u = user.into();
Rule::pred(move |ctx| ctx.message().map(|m| m.sender == u).unwrap_or(false))
}
pub fn keyword<I, S>(words: I) -> Rule
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let set: Vec<String> = words.into_iter().map(Into::into).collect();
Rule::pred(move |ctx| {
let Some(m) = ctx.message() else { return false };
let text = m.content.extract_text();
set.iter().any(|w| text.contains(w.as_str()))
})
}
#[derive(Clone, Debug, Default)]
pub struct Superusers(pub HashSet<Uin>);
pub fn superuser() -> Rule {
Rule::pred(|ctx| {
let Some(m) = ctx.message() else { return false };
ctx.state()
.get(&TypeId::of::<Superusers>())
.and_then(|a| a.downcast_ref::<Superusers>())
.map(|s| s.0.contains(&m.sender))
.unwrap_or(false)
})
}
#[derive(Clone, Copy)]
struct CachedRole(Option<Role>);
async fn sender_role(ctx: &Ctx) -> Option<Role> {
let m = ctx.message()?;
if let Some(c) = ctx.get_ext::<CachedRole>() {
return c.0;
}
let role = if let Some(member) = &m.member {
Some(member.role)
} else if m.peer.scene == Scene::Group {
match ctx.bot().get_group_member_info(m.peer.id, m.sender, false).await {
Ok(info) => Some(info.role),
Err(_) => None,
}
} else {
None
};
ctx.insert_ext(CachedRole(role));
role
}
struct RoleAtLeast {
owner_only: bool,
}
#[async_trait]
impl Check for RoleAtLeast {
async fn check(&self, ctx: &Ctx) -> bool {
match sender_role(ctx).await {
Some(Role::Owner) => true,
Some(Role::Admin) => !self.owner_only,
_ => false, }
}
}
pub fn group_admin() -> Rule {
Rule::new(RoleAtLeast { owner_only: false })
}
pub fn group_owner() -> Rule {
Rule::new(RoleAtLeast { owner_only: true })
}
#[derive(Clone)]
pub struct GateReply(pub Vec<Segment>);
pub fn replying<F>(rule: Rule, on_veto: F) -> Rule
where
F: Fn() -> Vec<Segment> + Send + Sync + 'static,
{
Rule::new(ReplyingCheck { rule, on_veto: Box::new(on_veto) })
}
struct ReplyingCheck {
rule: Rule,
on_veto: Box<dyn Fn() -> Vec<Segment> + Send + Sync + 'static>,
}
#[async_trait]
impl Check for ReplyingCheck {
async fn check(&self, ctx: &Ctx) -> bool {
if self.rule.eval(ctx).await {
return true;
}
ctx.insert_ext_if_absent(GateReply((self.on_veto)()));
false
}
}
#[derive(Default)]
pub struct KillSwitch(AtomicBool);
impl KillSwitch {
pub fn new() -> Self {
Self(AtomicBool::new(false))
}
pub fn set(&self, killed: bool) {
self.0.store(killed, Ordering::SeqCst);
}
pub fn is_killed(&self) -> bool {
self.0.load(Ordering::SeqCst)
}
}
pub fn switch(key: impl Into<SwitchKey>) -> Rule {
let key = key.into();
Rule::pred(move |ctx| {
let killed = ctx
.state()
.get(&TypeId::of::<KillSwitch>())
.and_then(|a| a.downcast_ref::<KillSwitch>())
.map(|k| k.is_killed())
.unwrap_or(false);
if killed {
return false;
}
let peer = ctx.event().peer();
match ctx
.state()
.get(&TypeId::of::<crate::enabled::EnabledSet>())
.and_then(|a| a.downcast_ref::<crate::enabled::EnabledSet>())
{
Some(es) => es.is_enabled(key.resolve(), true, peer),
None => true, }
})
}
#[derive(Default)]
pub struct SleepState(AtomicBool);
impl SleepState {
pub fn new() -> Self {
Self(AtomicBool::new(false))
}
pub fn set_asleep(&self, asleep: bool) {
self.0.store(asleep, Ordering::SeqCst);
}
pub fn is_asleep(&self) -> bool {
self.0.load(Ordering::SeqCst)
}
}
fn awake_inner() -> Rule {
Rule::pred(|ctx| {
ctx.state()
.get(&TypeId::of::<SleepState>())
.and_then(|a| a.downcast_ref::<SleepState>())
.map(|s| !s.is_asleep())
.unwrap_or(true)
})
}
pub fn awake() -> Rule {
replying(awake_inner(), || vec![Segment::text("Zzz")])
}
pub fn awake_silent() -> Rule {
awake_inner()
}