mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-20 17:28:54 +00:00
Compare commits
No commits in common. "a1468836201daac18441717cfa6cd20d651f927f" and "1a29bc095c934e71d3bc2223aeeb543b891bfee9" have entirely different histories.
a146883620
...
1a29bc095c
7 changed files with 220 additions and 21 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -1438,9 +1438,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
|||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.71"
|
||||
version = "0.10.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd"
|
||||
checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"cfg-if",
|
||||
|
@ -1470,9 +1470,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
|||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.106"
|
||||
version = "0.9.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd"
|
||||
checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
@ -1811,16 +1811,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
version = "0.17.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.12",
|
||||
"libc",
|
||||
"spin 0.9.8",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
11
README.md
11
README.md
|
@ -19,16 +19,9 @@ All PRs welcome.
|
|||
|
||||
### Regenerate compiler information
|
||||
|
||||
The commands expect the cwd to be at the project base directory.
|
||||
|
||||
Manually run migrations with
|
||||
```
|
||||
sqlx migrate run --database-url "sqlite:./youmubot.db" --source ./youmubot-db-sql/migrations
|
||||
```
|
||||
|
||||
Update compiler information with
|
||||
From within `./youmubot-db-sql` run
|
||||
```bash
|
||||
cargo sqlx prepare --database-url "sqlite:./youmubot.db" --workspace
|
||||
cargo sqlx prepare --database-url "sqlite:$(realpath ..)/youmubot.db"
|
||||
```
|
||||
|
||||
## License
|
||||
|
|
|
@ -7,13 +7,17 @@ use serenity::{
|
|||
},
|
||||
model::channel::{Channel, Message},
|
||||
};
|
||||
use soft_ban::{SOFT_BAN_COMMAND, SOFT_BAN_INIT_COMMAND};
|
||||
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)]
|
||||
#[commands(clean, ban, kick, soft_ban, soft_ban_init)]
|
||||
struct Admin;
|
||||
|
||||
#[command]
|
||||
|
|
164
youmubot-core/src/admin/soft_ban.rs
Normal file
164
youmubot-core/src/admin/soft_ban.rs
Normal file
|
@ -0,0 +1,164 @@
|
|||
use crate::db::{ServerSoftBans, SoftBans};
|
||||
use chrono::offset::Utc;
|
||||
use futures_util::{stream, TryStreamExt};
|
||||
use serenity::{
|
||||
framework::standard::{macros::command, Args, CommandResult},
|
||||
model::{channel::Message, id},
|
||||
};
|
||||
use youmubot_prelude::*;
|
||||
|
||||
#[command]
|
||||
#[required_permissions(ADMINISTRATOR)]
|
||||
#[description = "Soft-ban an user, might be with a certain amount of time. Re-banning an user removes the ban itself."]
|
||||
#[usage = "user#1234 [time]"]
|
||||
#[example = "user#1234 5s"]
|
||||
#[min_args(1)]
|
||||
#[max_args(2)]
|
||||
#[only_in("guilds")]
|
||||
pub async fn soft_ban(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let user = args.single::<UserId>()?.0.to_user(&ctx).await?;
|
||||
let data = ctx.data.read().await;
|
||||
let duration = if args.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(args.single::<args::Duration>()?)
|
||||
};
|
||||
let guild = msg
|
||||
.guild_id
|
||||
.ok_or_else(|| Error::msg("Command is guild only"))?;
|
||||
|
||||
let mut db = SoftBans::open(&data);
|
||||
let val = db
|
||||
.borrow()?
|
||||
.get(&guild)
|
||||
.map(|v| (v.role, v.periodical_bans.get(&user.id).cloned()));
|
||||
let (role, current_ban_deadline) = match val {
|
||||
None => {
|
||||
msg.reply(&ctx, "⚠ This server has not enabled the soft-ban feature. Check out `y!a soft-ban-init`.").await?;
|
||||
return Ok(());
|
||||
}
|
||||
Some(v) => v,
|
||||
};
|
||||
|
||||
let member = guild.member(&ctx, &user).await?;
|
||||
match duration {
|
||||
None if member.roles.contains(&role) => {
|
||||
msg.reply(&ctx, format!("⛓ Lifting soft-ban for user {}.", user.tag()))
|
||||
.await?;
|
||||
member.remove_role(&ctx, role).await?;
|
||||
return Ok(());
|
||||
}
|
||||
None => {
|
||||
msg.reply(&ctx, format!("⛓ Soft-banning user {}.", user.tag()))
|
||||
.await?;
|
||||
}
|
||||
Some(v) => {
|
||||
// Add the duration into the ban timeout.
|
||||
let until =
|
||||
current_ban_deadline.unwrap_or_else(Utc::now) + chrono::Duration::from_std(v.0)?;
|
||||
msg.reply(
|
||||
&ctx,
|
||||
format!("⛓ Soft-banning user {} until {}.", user.tag(), until),
|
||||
)
|
||||
.await?;
|
||||
db.borrow_mut()?
|
||||
.get_mut(&guild)
|
||||
.map(|v| v.periodical_bans.insert(user.id, until));
|
||||
}
|
||||
}
|
||||
member.add_role(&ctx, role).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[required_permissions(ADMINISTRATOR)]
|
||||
#[description = "Sets up the soft-ban command. This command can only be run once.\nThe soft-ban command assigns a role, temporarily, to a user."]
|
||||
#[usage = "{soft_ban_role_id}"]
|
||||
#[num_args(1)]
|
||||
#[only_in("guilds")]
|
||||
pub async fn soft_ban_init(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let role_id = args.single::<RoleId>()?.0;
|
||||
let data = ctx.data.read().await;
|
||||
let guild = msg.guild_id.unwrap().to_partial_guild(&ctx).await?;
|
||||
// Check whether the role_id is the one we wanted
|
||||
if !guild.roles.contains_key(&role_id) {
|
||||
return Err(Error::msg(format!("{} is not a role in this server.", role_id)).into());
|
||||
}
|
||||
// Check if we already set up
|
||||
let mut db = SoftBans::open(&data);
|
||||
let set_up = db.borrow()?.contains_key(&guild.id);
|
||||
|
||||
if !set_up {
|
||||
db.borrow_mut()?
|
||||
.insert(guild.id, ServerSoftBans::new(role_id));
|
||||
msg.react(&ctx, '👌').await?;
|
||||
} else {
|
||||
return Err(Error::msg("Server already set up soft-bans.").into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Watch the soft bans. Blocks forever.
|
||||
pub async fn watch_soft_bans(cache_http: impl CacheHttp, data: AppData) {
|
||||
loop {
|
||||
// Scope so that locks are released
|
||||
{
|
||||
// Poll the data for any changes.
|
||||
let data = data.read().await;
|
||||
let mut data = SoftBans::open(&data);
|
||||
let mut db = data.borrow().unwrap().clone();
|
||||
let now = Utc::now();
|
||||
for (server_id, bans) in db.iter_mut() {
|
||||
let server_name: String = match server_id.to_partial_guild(cache_http.http()).await
|
||||
{
|
||||
Err(_) => continue,
|
||||
Ok(v) => v.name,
|
||||
};
|
||||
let to_remove: Vec<_> = bans
|
||||
.periodical_bans
|
||||
.iter()
|
||||
.filter_map(|(user, time)| if time <= &now { Some(user) } else { None })
|
||||
.cloned()
|
||||
.collect();
|
||||
if let Err(e) = to_remove
|
||||
.into_iter()
|
||||
.map(|user_id| {
|
||||
bans.periodical_bans.remove(&user_id);
|
||||
lift_soft_ban_for(
|
||||
&cache_http,
|
||||
*server_id,
|
||||
&server_name[..],
|
||||
bans.role,
|
||||
user_id,
|
||||
)
|
||||
})
|
||||
.collect::<stream::FuturesUnordered<_>>()
|
||||
.try_collect::<()>()
|
||||
.await
|
||||
{
|
||||
eprintln!("Error while scanning soft-bans list: {}", e)
|
||||
}
|
||||
}
|
||||
*(data.borrow_mut().unwrap()) = db;
|
||||
}
|
||||
// Sleep the thread for a minute
|
||||
tokio::time::sleep(std::time::Duration::from_secs(60)).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn lift_soft_ban_for(
|
||||
cache_http: impl CacheHttp,
|
||||
server_id: id::GuildId,
|
||||
server_name: &str,
|
||||
ban_role: id::RoleId,
|
||||
user_id: id::UserId,
|
||||
) -> Result<()> {
|
||||
let m = server_id.member(&cache_http, user_id).await?;
|
||||
println!(
|
||||
"Soft-ban for `{}` in server `{}` unlifted.",
|
||||
m.user.name, server_name
|
||||
);
|
||||
m.remove_role(cache_http.http(), ban_role).await?;
|
||||
Ok(())
|
||||
}
|
|
@ -1,15 +1,42 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serenity::model::{
|
||||
channel::ReactionType,
|
||||
id::{MessageId, RoleId},
|
||||
id::{MessageId, RoleId, UserId},
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use youmubot_db::{GuildMap, DB};
|
||||
use youmubot_prelude::*;
|
||||
|
||||
/// A list of SoftBans for all servers.
|
||||
pub type SoftBans = DB<GuildMap<ServerSoftBans>>;
|
||||
|
||||
/// A list of assignable roles for all servers.
|
||||
pub type Roles = DB<GuildMap<RoleList>>;
|
||||
|
||||
/// For the admin commands:
|
||||
/// - Each server might have a `soft ban` role implemented.
|
||||
/// - We allow periodical `soft ban` applications.
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ServerSoftBans {
|
||||
/// The soft-ban role.
|
||||
pub role: RoleId,
|
||||
/// List of all to-unban people.
|
||||
pub periodical_bans: HashMap<UserId, DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl ServerSoftBans {
|
||||
// Create a new, implemented role.
|
||||
pub fn new(role: RoleId) -> Self {
|
||||
Self {
|
||||
role,
|
||||
periodical_bans: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a server's role list.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct RoleList {
|
||||
|
|
|
@ -10,7 +10,7 @@ use serenity::{
|
|||
pub use admin::ADMIN_GROUP;
|
||||
pub use community::COMMUNITY_GROUP;
|
||||
pub use fun::FUN_GROUP;
|
||||
use youmubot_prelude::*;
|
||||
use youmubot_prelude::{announcer::CacheAndHttp, *};
|
||||
|
||||
pub mod admin;
|
||||
pub mod community;
|
||||
|
@ -43,6 +43,7 @@ impl<T: AsRef<CoreEnv> + Send + Sync> HasCoreEnv for T {
|
|||
|
||||
/// Sets up all databases in the client.
|
||||
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,
|
||||
&path.join("roles_v2.yaml"),
|
||||
|
@ -55,6 +56,15 @@ pub async fn setup(path: &std::path::Path, data: &mut TypeMap, prelude: Env) ->
|
|||
CoreEnv::new(prelude).await
|
||||
}
|
||||
|
||||
pub fn ready_hook(ctx: &Context) -> CommandResult {
|
||||
// Create handler threads
|
||||
tokio::spawn(admin::watch_soft_bans(
|
||||
CacheAndHttp::from_context(ctx),
|
||||
ctx.data.clone(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// A help command
|
||||
#[help]
|
||||
pub async fn help(
|
||||
|
|
|
@ -38,7 +38,6 @@ impl Handler {
|
|||
self.hooks.push(RwLock::new(Box::new(f)));
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn push_ready_hook(&mut self, f: fn(&Context) -> CommandResult) {
|
||||
self.ready_hooks.push(f);
|
||||
}
|
||||
|
@ -185,6 +184,8 @@ async fn main() {
|
|||
}
|
||||
|
||||
let mut handler = Handler::new();
|
||||
#[cfg(feature = "core")]
|
||||
handler.push_ready_hook(youmubot_core::ready_hook);
|
||||
// Set up hooks
|
||||
#[cfg(feature = "osu")]
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue