ignore command (#59)
Some checks are pending
Build and Test / Format check (push) Waiting to run
Build and Test / Lint (push) Waiting to run
Build and Test / Test (push) Waiting to run
Build and Test / Build (push) Waiting to run

* New table `ignored_users` and queries

* Update sqlx to 0.8

* Implement `ignore` command and add needed hooks
This commit is contained in:
Natsu Kagami 2025-03-27 15:13:00 +01:00 committed by GitHub
parent a36fa87964
commit 7c0cbea716
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 580 additions and 383 deletions

View file

@ -0,0 +1,152 @@
use std::sync::Arc;
use chrono::{DateTime, Utc};
use dashmap::DashMap;
use serenity::all::User;
use youmubot_db_sql::models::ignore_list as model;
use youmubot_prelude::*;
use crate::HasCoreEnv;
// Should we ignore this user?
pub fn should_ignore(env: &impl HasCoreEnv, id: UserId) -> bool {
env.core_env().ignore.query(id).is_some()
}
/// Ignore: make Youmu ignore all commands from an user.
#[poise::command(
slash_command,
subcommands("add", "remove", "list"),
owners_only,
install_context = "User",
interaction_context = "Guild|BotDm"
)]
pub async fn ignore<U: HasCoreEnv>(_ctx: CmdContext<'_, U>) -> Result<()> {
Ok(())
}
/// Add an user to ignore list.
#[poise::command(slash_command, owners_only)]
async fn add<U: HasCoreEnv>(
ctx: CmdContext<'_, U>,
#[description = "Discord username"] discord_name: User,
) -> Result<()> {
let env = ctx.data().core_env();
ctx.defer().await?;
let msg = format!("User **{}** ignored!", discord_name.name);
env.ignore
.add(&env.prelude, UserId(discord_name.id), discord_name.name)
.await?;
ctx.say(msg).await?;
Ok(())
}
/// Remove an user from ignore list.
#[poise::command(slash_command, owners_only)]
async fn remove<U: HasCoreEnv>(
ctx: CmdContext<'_, U>,
#[description = "Discord username"] discord_name: User,
) -> Result<()> {
let env = ctx.data().core_env();
ctx.defer().await?;
env.ignore
.remove(&env.prelude, UserId(discord_name.id))
.await?;
let msg = format!("User **{}** removed from ignore list!", discord_name.name);
ctx.say(msg).await?;
Ok(())
}
/// List ignored users.
#[poise::command(slash_command, owners_only)]
async fn list<U: HasCoreEnv>(ctx: CmdContext<'_, U>) -> Result<()> {
let env = ctx.data().core_env();
let is_dm = ctx.guild_id().is_none();
ctx.defer().await?;
let users = env
.ignore
.list
.clone()
.iter()
.map(|v| {
format!(
"- {} ({}), since <t:{}:R>",
v.username,
if is_dm {
v.id.0.mention().to_string()
} else {
format!("`{}`", v.id.0.get())
},
v.ignored_since.timestamp(),
)
})
.collect::<Vec<_>>()
.join("\n");
let users = if users == "" {
"No one is being ignored!"
} else {
&users[..]
};
let msg = format!("Ignored users:\n{}", users);
ctx.say(msg).await?;
Ok(())
}
#[derive(Debug, Clone)]
pub(crate) struct IgnoredUsers {
list: Arc<DashMap<UserId, IgnoredUser>>,
}
impl IgnoredUsers {
pub async fn from_db(env: &Env) -> Result<Self> {
let list = model::IgnoredUser::get_all(&env.sql).await?;
let mp: DashMap<_, _> = list
.into_iter()
.map(|v| {
let id = (v.id as u64).into();
(
id,
IgnoredUser {
id,
username: v.username,
ignored_since: v.ignored_since,
},
)
})
.collect();
Ok(Self { list: Arc::new(mp) })
}
pub fn query<'a>(
&'a self,
id: UserId,
) -> Option<impl std::ops::Deref<Target = IgnoredUser> + 'a> {
self.list.get(&id)
}
pub async fn add(&self, env: &Env, id: UserId, username: String) -> Result<()> {
let iu = model::IgnoredUser::add(&env.sql, id.0.get() as i64, username).await?;
self.list.insert(
id,
IgnoredUser {
id,
username: iu.username,
ignored_since: iu.ignored_since,
},
);
Ok(())
}
pub async fn remove(&self, env: &Env, id: UserId) -> Result<bool> {
model::IgnoredUser::remove(&env.sql, id.0.get() as i64).await?;
Ok(self.list.remove(&id).is_some())
}
}
#[derive(Debug, Clone)]
pub(crate) struct IgnoredUser {
pub id: UserId,
pub username: String,
pub ignored_since: DateTime<Utc>,
}

View file

@ -13,6 +13,8 @@ use youmubot_prelude::*;
mod soft_ban;
pub use soft_ban::watch_soft_bans;
pub mod ignore;
#[group]
#[description = "Administrative commands for the server."]
#[commands(clean, ban, kick, soft_ban, soft_ban_init)]

View file

@ -17,11 +17,32 @@ pub mod community;
mod db;
pub mod fun;
#[derive(Debug, Clone)]
pub struct CoreEnv {
pub(crate) prelude: Env,
pub(crate) ignore: admin::ignore::IgnoredUsers,
}
impl CoreEnv {
async fn new(prelude: Env) -> Result<Self> {
let ignore = admin::ignore::IgnoredUsers::from_db(&prelude).await?;
Ok(Self { prelude, ignore })
}
}
/// Gets an [CoreEnv] from the current environment.
pub trait HasCoreEnv: Send + Sync {
fn core_env(&self) -> &CoreEnv;
}
impl<T: AsRef<CoreEnv> + Send + Sync> HasCoreEnv for T {
fn core_env(&self) -> &CoreEnv {
self.as_ref()
}
}
/// Sets up all databases in the client.
pub fn setup(
path: &std::path::Path,
data: &mut TypeMap,
) -> serenity::framework::standard::CommandResult {
pub async fn setup(path: &std::path::Path, data: &mut TypeMap, prelude: Env) -> Result<CoreEnv> {
db::SoftBans::insert_into(&mut *data, &path.join("soft_bans.yaml"))?;
db::load_role_list(
&mut *data,
@ -32,7 +53,7 @@ pub fn setup(
// Start reaction handlers
data.insert::<community::ReactionWatchers>(community::ReactionWatchers::new(&*data)?);
Ok(())
CoreEnv::new(prelude).await
}
pub fn ready_hook(ctx: &Context) -> CommandResult {