mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-18 16:28:55 +00:00
Implement roles
management (#18)
Fix formatting Implement `roles` management Add a 'roles' mod and DB
This commit is contained in:
parent
99c7888dfb
commit
5dd26d7474
4 changed files with 250 additions and 1 deletions
|
@ -15,14 +15,16 @@ use serenity::{
|
||||||
};
|
};
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
|
mod roles;
|
||||||
mod votes;
|
mod votes;
|
||||||
|
|
||||||
|
use roles::{ADD_COMMAND, LIST_COMMAND, REMOVE_COMMAND, TOGGLE_COMMAND};
|
||||||
use votes::VOTE_COMMAND;
|
use votes::VOTE_COMMAND;
|
||||||
|
|
||||||
#[group]
|
#[group]
|
||||||
#[description = "Community related commands. Usually comes with some sort of delays, since it involves pinging"]
|
#[description = "Community related commands. Usually comes with some sort of delays, since it involves pinging"]
|
||||||
#[only_in("guilds")]
|
#[only_in("guilds")]
|
||||||
#[commands(choose, vote)]
|
#[commands(choose, vote, add, list, remove, toggle)]
|
||||||
struct Community;
|
struct Community;
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
|
|
236
youmubot-core/src/community/roles.rs
Normal file
236
youmubot-core/src/community/roles.rs
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
use crate::db::Roles as DB;
|
||||||
|
use serenity::{
|
||||||
|
framework::standard::{macros::command, Args, CommandError as Error, CommandResult},
|
||||||
|
model::{channel::Message, id::RoleId},
|
||||||
|
utils::MessageBuilder,
|
||||||
|
};
|
||||||
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
|
#[command("listroles")]
|
||||||
|
#[description = "List all available roles in the server."]
|
||||||
|
#[num_args(0)]
|
||||||
|
#[only_in(guilds)]
|
||||||
|
fn list(ctx: &mut Context, m: &Message, _: Args) -> CommandResult {
|
||||||
|
let guild_id = m.guild_id.unwrap(); // only_in(guilds)
|
||||||
|
|
||||||
|
let db = DB::open(&*ctx.data.read());
|
||||||
|
let db = db.borrow()?;
|
||||||
|
let roles = db.get(&guild_id).filter(|v| !v.is_empty());
|
||||||
|
match roles {
|
||||||
|
None => {
|
||||||
|
m.reply(&ctx, "No roles available for assigning.")?;
|
||||||
|
}
|
||||||
|
Some(v) => {
|
||||||
|
let roles = guild_id.to_partial_guild(&ctx)?.roles;
|
||||||
|
let roles: Vec<_> = v
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(_, role)| roles.get(&role.id).map(|r| (r, &role.description)))
|
||||||
|
.collect();
|
||||||
|
const ROLES_PER_PAGE: usize = 5;
|
||||||
|
let pages = (roles.len() + ROLES_PER_PAGE - 1) / ROLES_PER_PAGE;
|
||||||
|
|
||||||
|
let watcher = ctx.data.get_cloned::<ReactionWatcher>();
|
||||||
|
watcher.paginate_fn(
|
||||||
|
ctx.clone(),
|
||||||
|
m.channel_id,
|
||||||
|
|page, e| {
|
||||||
|
let page = page as usize;
|
||||||
|
let start = page * ROLES_PER_PAGE;
|
||||||
|
let end = roles.len().min(start + ROLES_PER_PAGE);
|
||||||
|
if end <= start {
|
||||||
|
return (e, Err(Error::from("No more roles to display")));
|
||||||
|
}
|
||||||
|
let roles = &roles[start..end];
|
||||||
|
let nw = roles // name width
|
||||||
|
.iter()
|
||||||
|
.map(|(r, _)| r.name.len())
|
||||||
|
.max()
|
||||||
|
.unwrap()
|
||||||
|
.max(6);
|
||||||
|
let idw = roles[0].0.id.to_string().len();
|
||||||
|
let dw = roles
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.1.len())
|
||||||
|
.max()
|
||||||
|
.unwrap()
|
||||||
|
.max(" Description ".len());
|
||||||
|
let mut m = MessageBuilder::new();
|
||||||
|
m.push_line("```");
|
||||||
|
|
||||||
|
// Table header
|
||||||
|
m.push_line(format!(
|
||||||
|
"{:nw$} | {:idw$} | {:dw$}",
|
||||||
|
"Name",
|
||||||
|
"ID",
|
||||||
|
"Description",
|
||||||
|
nw = nw,
|
||||||
|
idw = idw,
|
||||||
|
dw = dw,
|
||||||
|
));
|
||||||
|
m.push_line(format!(
|
||||||
|
"{:->nw$}---{:->idw$}---{:->dw$}",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nw = nw,
|
||||||
|
idw = idw,
|
||||||
|
dw = dw,
|
||||||
|
));
|
||||||
|
|
||||||
|
for (role, description) in roles.iter() {
|
||||||
|
m.push_line(format!(
|
||||||
|
"{:nw$} | {:idw$} | {:dw$}",
|
||||||
|
role.name,
|
||||||
|
role.id,
|
||||||
|
description,
|
||||||
|
nw = nw,
|
||||||
|
idw = idw,
|
||||||
|
dw = dw,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
m.push_line("```");
|
||||||
|
m.push(format!("Page **{}/{}**", page + 1, pages));
|
||||||
|
|
||||||
|
(e.content(m.build()), Ok(()))
|
||||||
|
},
|
||||||
|
std::time::Duration::from_secs(60 * 10),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command("role")]
|
||||||
|
#[description = "Toggle a role by its name or ID."]
|
||||||
|
#[num_args(1)]
|
||||||
|
#[only_in(guilds)]
|
||||||
|
fn toggle(ctx: &mut Context, m: &Message, mut args: Args) -> CommandResult {
|
||||||
|
let role = args.single::<String>()?;
|
||||||
|
let guild_id = m.guild_id.unwrap();
|
||||||
|
let roles = guild_id.to_partial_guild(&ctx)?.roles;
|
||||||
|
let role = match role.parse::<u64>() {
|
||||||
|
Ok(id) => roles.get(&RoleId(id)).cloned(),
|
||||||
|
Err(_) => roles
|
||||||
|
.iter()
|
||||||
|
.find_map(|(_, r)| if r.name == role { Some(r) } else { None })
|
||||||
|
.cloned(),
|
||||||
|
};
|
||||||
|
match role {
|
||||||
|
None => {
|
||||||
|
m.reply(&ctx, "No such role exists")?;
|
||||||
|
}
|
||||||
|
Some(role)
|
||||||
|
if !DB::open(&*ctx.data.read())
|
||||||
|
.borrow()?
|
||||||
|
.get(&guild_id)
|
||||||
|
.map(|g| g.contains_key(&role.id))
|
||||||
|
.unwrap_or(false) =>
|
||||||
|
{
|
||||||
|
m.reply(&ctx, "This role is not self-assignable. Check the `listroles` command to see which role can be assigned.")?;
|
||||||
|
}
|
||||||
|
Some(role) => {
|
||||||
|
let mut member = m.member(&ctx).ok_or(Error::from("Cannot find member"))?;
|
||||||
|
if member.roles.contains(&role.id) {
|
||||||
|
member.remove_role(&ctx, &role)?;
|
||||||
|
m.reply(&ctx, format!("Role `{}` has been removed.", role.name))?;
|
||||||
|
} else {
|
||||||
|
member.add_role(&ctx, &role)?;
|
||||||
|
m.reply(&ctx, format!("Role `{}` has been assigned.", role.name))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command("addrole")]
|
||||||
|
#[description = "Add a role as the assignable role"]
|
||||||
|
#[usage = "{role-name-or-id} / {description}"]
|
||||||
|
#[example = "hd820 / Headphones role"]
|
||||||
|
#[num_args(2)]
|
||||||
|
#[required_permissions(MANAGE_ROLES)]
|
||||||
|
#[only_in(guilds)]
|
||||||
|
fn add(ctx: &mut Context, m: &Message, mut args: Args) -> CommandResult {
|
||||||
|
let role = args.single::<String>()?;
|
||||||
|
let description = args.single::<String>()?;
|
||||||
|
let guild_id = m.guild_id.unwrap();
|
||||||
|
let roles = guild_id.to_partial_guild(&ctx)?.roles;
|
||||||
|
let role = match role.parse::<u64>() {
|
||||||
|
Ok(id) => roles.get(&RoleId(id)).cloned(),
|
||||||
|
Err(_) => roles
|
||||||
|
.iter()
|
||||||
|
.find_map(|(_, r)| if r.name == role { Some(r) } else { None })
|
||||||
|
.cloned(),
|
||||||
|
};
|
||||||
|
match role {
|
||||||
|
None => {
|
||||||
|
m.reply(&ctx, "No such role exists")?;
|
||||||
|
}
|
||||||
|
Some(role)
|
||||||
|
if DB::open(&*ctx.data.read())
|
||||||
|
.borrow()?
|
||||||
|
.get(&guild_id)
|
||||||
|
.map(|g| g.contains_key(&role.id))
|
||||||
|
.unwrap_or(false) =>
|
||||||
|
{
|
||||||
|
m.reply(&ctx, "This role already exists in the database.")?;
|
||||||
|
}
|
||||||
|
Some(role) => {
|
||||||
|
DB::open(&*ctx.data.read())
|
||||||
|
.borrow_mut()?
|
||||||
|
.entry(guild_id)
|
||||||
|
.or_default()
|
||||||
|
.insert(
|
||||||
|
role.id,
|
||||||
|
crate::db::Role {
|
||||||
|
id: role.id,
|
||||||
|
description,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
m.react(&ctx, "👌🏼")?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command("removerole")]
|
||||||
|
#[description = "Remove a role from the assignable roles list."]
|
||||||
|
#[usage = "{role-name-or-id}"]
|
||||||
|
#[example = "hd820"]
|
||||||
|
#[num_args(1)]
|
||||||
|
#[required_permissions(MANAGE_ROLES)]
|
||||||
|
#[only_in(guilds)]
|
||||||
|
fn remove(ctx: &mut Context, m: &Message, mut args: Args) -> CommandResult {
|
||||||
|
let role = args.single::<String>()?;
|
||||||
|
let guild_id = m.guild_id.unwrap();
|
||||||
|
let roles = guild_id.to_partial_guild(&ctx)?.roles;
|
||||||
|
let role = match role.parse::<u64>() {
|
||||||
|
Ok(id) => roles.get(&RoleId(id)).cloned(),
|
||||||
|
Err(_) => roles
|
||||||
|
.iter()
|
||||||
|
.find_map(|(_, r)| if r.name == role { Some(r) } else { None })
|
||||||
|
.cloned(),
|
||||||
|
};
|
||||||
|
match role {
|
||||||
|
None => {
|
||||||
|
m.reply(&ctx, "No such role exists")?;
|
||||||
|
}
|
||||||
|
Some(role)
|
||||||
|
if !DB::open(&*ctx.data.read())
|
||||||
|
.borrow()?
|
||||||
|
.get(&guild_id)
|
||||||
|
.map(|g| g.contains_key(&role.id))
|
||||||
|
.unwrap_or(false) =>
|
||||||
|
{
|
||||||
|
m.reply(&ctx, "This role does not exist in the assignable list.")?;
|
||||||
|
}
|
||||||
|
Some(role) => {
|
||||||
|
DB::open(&*ctx.data.read())
|
||||||
|
.borrow_mut()?
|
||||||
|
.entry(guild_id)
|
||||||
|
.or_default()
|
||||||
|
.remove(&role.id);
|
||||||
|
m.react(&ctx, "👌🏼")?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -8,6 +8,9 @@ use youmubot_db::{GuildMap, DB};
|
||||||
/// A list of SoftBans for all servers.
|
/// A list of SoftBans for all servers.
|
||||||
pub type SoftBans = DB<GuildMap<ServerSoftBans>>;
|
pub type SoftBans = DB<GuildMap<ServerSoftBans>>;
|
||||||
|
|
||||||
|
/// A list of assignable roles for all servers.
|
||||||
|
pub type Roles = DB<GuildMap<HashMap<RoleId, Role>>>;
|
||||||
|
|
||||||
/// For the admin commands:
|
/// For the admin commands:
|
||||||
/// - Each server might have a `soft ban` role implemented.
|
/// - Each server might have a `soft ban` role implemented.
|
||||||
/// - We allow periodical `soft ban` applications.
|
/// - We allow periodical `soft ban` applications.
|
||||||
|
@ -34,3 +37,10 @@ pub struct ImplementedSoftBans {
|
||||||
/// List of all to-unban people.
|
/// List of all to-unban people.
|
||||||
pub periodical_bans: HashMap<UserId, DateTime<Utc>>,
|
pub periodical_bans: HashMap<UserId, DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Role represents an assignable role.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Role {
|
||||||
|
pub id: RoleId,
|
||||||
|
pub description: String,
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ pub fn setup(
|
||||||
data: &mut youmubot_prelude::ShareMap,
|
data: &mut youmubot_prelude::ShareMap,
|
||||||
) -> serenity::framework::standard::CommandResult {
|
) -> serenity::framework::standard::CommandResult {
|
||||||
db::SoftBans::insert_into(&mut *data, &path.join("soft_bans.yaml"))?;
|
db::SoftBans::insert_into(&mut *data, &path.join("soft_bans.yaml"))?;
|
||||||
|
db::Roles::insert_into(&mut *data, &path.join("roles.yaml"))?;
|
||||||
|
|
||||||
// Create handler threads
|
// Create handler threads
|
||||||
std::thread::spawn(admin::watch_soft_bans(client));
|
std::thread::spawn(admin::watch_soft_bans(client));
|
||||||
|
|
Loading…
Add table
Reference in a new issue