mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-19 16:58:55 +00:00
Extended role list
This commit is contained in:
parent
0bbd50386e
commit
72c574fb48
4 changed files with 107 additions and 21 deletions
|
@ -1,7 +1,11 @@
|
||||||
use crate::db::Roles as DB;
|
use crate::db::Roles as DB;
|
||||||
use serenity::{
|
use serenity::{
|
||||||
framework::standard::{macros::command, Args, CommandResult},
|
framework::standard::{macros::command, Args, CommandResult},
|
||||||
model::{channel::Message, guild::Role, id::RoleId},
|
model::{
|
||||||
|
channel::{Message, ReactionType},
|
||||||
|
guild::Role,
|
||||||
|
id::RoleId,
|
||||||
|
},
|
||||||
utils::MessageBuilder,
|
utils::MessageBuilder,
|
||||||
};
|
};
|
||||||
use youmubot_prelude::*;
|
use youmubot_prelude::*;
|
||||||
|
@ -18,7 +22,7 @@ async fn list(ctx: &Context, m: &Message, _: Args) -> CommandResult {
|
||||||
let roles = db
|
let roles = db
|
||||||
.borrow()?
|
.borrow()?
|
||||||
.get(&guild_id)
|
.get(&guild_id)
|
||||||
.filter(|v| !v.is_empty())
|
.filter(|v| !v.roles.is_empty())
|
||||||
.cloned();
|
.cloned();
|
||||||
match roles {
|
match roles {
|
||||||
None => {
|
None => {
|
||||||
|
@ -27,6 +31,7 @@ async fn list(ctx: &Context, m: &Message, _: Args) -> CommandResult {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
let roles = guild_id.to_partial_guild(&ctx).await?.roles;
|
let roles = guild_id.to_partial_guild(&ctx).await?.roles;
|
||||||
let roles: Vec<_> = v
|
let roles: Vec<_> = v
|
||||||
|
.roles
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|(_, role)| roles.get(&role.id).cloned().map(|r| (r, role.description)))
|
.filter_map(|(_, role)| roles.get(&role.id).cloned().map(|r| (r, role.description)))
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -128,7 +133,7 @@ async fn toggle(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
||||||
if !DB::open(&*ctx.data.read().await)
|
if !DB::open(&*ctx.data.read().await)
|
||||||
.borrow()?
|
.borrow()?
|
||||||
.get(&guild_id)
|
.get(&guild_id)
|
||||||
.map(|g| g.contains_key(&role.id))
|
.map(|g| g.roles.contains_key(&role.id))
|
||||||
.unwrap_or(false) =>
|
.unwrap_or(false) =>
|
||||||
{
|
{
|
||||||
m.reply(&ctx, "This role is not self-assignable. Check the `listroles` command to see which role can be assigned.").await?;
|
m.reply(&ctx, "This role is not self-assignable. Check the `listroles` command to see which role can be assigned.").await?;
|
||||||
|
@ -150,16 +155,32 @@ async fn toggle(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command("addrole")]
|
#[command("addrole")]
|
||||||
#[description = "Add a role as the assignable role"]
|
#[description = "Add a role as the assignable role. Overrides the old entry."]
|
||||||
#[usage = "{role-name-or-id} / {description}"]
|
#[usage = "{role-name-or-id} / {description} / [representing emoji = none]"]
|
||||||
#[example = "hd820 / Headphones role"]
|
#[example = "hd820 / Headphones role / 🎧"]
|
||||||
#[num_args(2)]
|
#[min_args(2)]
|
||||||
|
#[max_args(3)]
|
||||||
#[required_permissions(MANAGE_ROLES)]
|
#[required_permissions(MANAGE_ROLES)]
|
||||||
#[only_in(guilds)]
|
#[only_in(guilds)]
|
||||||
async fn add(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
async fn add(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
||||||
let role = args.single_quoted::<String>()?;
|
let role = args.single_quoted::<String>()?;
|
||||||
let data = ctx.data.read().await;
|
let data = ctx.data.read().await;
|
||||||
let description = args.single::<String>()?;
|
let description = args.single_quoted::<String>()?;
|
||||||
|
let reaction = match args.single::<ReactionType>() {
|
||||||
|
Ok(v) => match &v {
|
||||||
|
ReactionType::Custom { id, .. } => {
|
||||||
|
// Verify that the reaction type is from the server.
|
||||||
|
if m.guild_id.unwrap().emoji(&ctx, *id).await.is_err() {
|
||||||
|
m.reply(&ctx, "Emote cannot be used as I cannot send this back.")
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Some(v)
|
||||||
|
}
|
||||||
|
_ => Some(v),
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
let guild_id = m.guild_id.unwrap();
|
let guild_id = m.guild_id.unwrap();
|
||||||
let roles = guild_id.to_partial_guild(&ctx).await?.roles;
|
let roles = guild_id.to_partial_guild(&ctx).await?.roles;
|
||||||
let role = role_from_string(&role, &roles);
|
let role = role_from_string(&role, &roles);
|
||||||
|
@ -167,26 +188,18 @@ async fn add(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
||||||
None => {
|
None => {
|
||||||
m.reply(&ctx, "No such role exists").await?;
|
m.reply(&ctx, "No such role exists").await?;
|
||||||
}
|
}
|
||||||
Some(role)
|
|
||||||
if DB::open(&*data)
|
|
||||||
.borrow()?
|
|
||||||
.get(&guild_id)
|
|
||||||
.map(|g| g.contains_key(&role.id))
|
|
||||||
.unwrap_or(false) =>
|
|
||||||
{
|
|
||||||
m.reply(&ctx, "This role already exists in the database.")
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
Some(role) => {
|
Some(role) => {
|
||||||
DB::open(&*data)
|
DB::open(&*data)
|
||||||
.borrow_mut()?
|
.borrow_mut()?
|
||||||
.entry(guild_id)
|
.entry(guild_id)
|
||||||
.or_default()
|
.or_default()
|
||||||
|
.roles
|
||||||
.insert(
|
.insert(
|
||||||
role.id,
|
role.id,
|
||||||
crate::db::Role {
|
crate::db::Role {
|
||||||
id: role.id,
|
id: role.id,
|
||||||
description,
|
description,
|
||||||
|
reaction,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
m.react(&ctx, '👌').await?;
|
m.react(&ctx, '👌').await?;
|
||||||
|
@ -216,7 +229,7 @@ async fn remove(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
||||||
if !DB::open(&*data)
|
if !DB::open(&*data)
|
||||||
.borrow()?
|
.borrow()?
|
||||||
.get(&guild_id)
|
.get(&guild_id)
|
||||||
.map(|g| g.contains_key(&role.id))
|
.map(|g| g.roles.contains_key(&role.id))
|
||||||
.unwrap_or(false) =>
|
.unwrap_or(false) =>
|
||||||
{
|
{
|
||||||
m.reply(&ctx, "This role does not exist in the assignable list.")
|
m.reply(&ctx, "This role does not exist in the assignable list.")
|
||||||
|
@ -227,6 +240,7 @@ async fn remove(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
|
||||||
.borrow_mut()?
|
.borrow_mut()?
|
||||||
.entry(guild_id)
|
.entry(guild_id)
|
||||||
.or_default()
|
.or_default()
|
||||||
|
.roles
|
||||||
.remove(&role.id);
|
.remove(&role.id);
|
||||||
m.react(&ctx, '👌').await?;
|
m.react(&ctx, '👌').await?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,13 @@ use serde::{Deserialize, Serialize};
|
||||||
use serenity::model::id::{RoleId, UserId};
|
use serenity::model::id::{RoleId, UserId};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use youmubot_db::{GuildMap, DB};
|
use youmubot_db::{GuildMap, DB};
|
||||||
|
use youmubot_prelude::*;
|
||||||
|
|
||||||
/// 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.
|
/// A list of assignable roles for all servers.
|
||||||
pub type Roles = DB<GuildMap<HashMap<RoleId, Role>>>;
|
pub type Roles = DB<GuildMap<RoleList>>;
|
||||||
|
|
||||||
/// 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.
|
||||||
|
@ -33,9 +34,71 @@ impl ServerSoftBans {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a server's role list.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||||
|
pub struct RoleList {
|
||||||
|
/// `reaction_message` handles the reaction-handling message.
|
||||||
|
pub reaction_message: Option<serenity::model::id::MessageId>,
|
||||||
|
pub roles: HashMap<RoleId, Role>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the file list, handling migration from v1.
|
||||||
|
pub fn load_role_list(
|
||||||
|
map: &mut TypeMap,
|
||||||
|
path: impl AsRef<std::path::Path>,
|
||||||
|
v1_path: impl AsRef<std::path::Path>,
|
||||||
|
) -> Result<()> {
|
||||||
|
// Try to load v2 first
|
||||||
|
let v2 = Roles::load_from_path(path.as_ref());
|
||||||
|
let v2 = match v2 {
|
||||||
|
Ok(v2) => {
|
||||||
|
map.insert::<Roles>(v2);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(v2) => v2,
|
||||||
|
};
|
||||||
|
// Try migrating from v1.
|
||||||
|
match legacy::RolesV1::load_from_path(v1_path.as_ref()) {
|
||||||
|
Ok(v1) => {
|
||||||
|
Roles::insert_into(map, path)?;
|
||||||
|
*Roles::open(&map).borrow_mut()? = v1
|
||||||
|
.get_data(true)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(guild, roles)| {
|
||||||
|
(
|
||||||
|
guild,
|
||||||
|
RoleList {
|
||||||
|
reaction_message: None,
|
||||||
|
roles,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
std::fs::remove_file(v1_path.as_ref()).pls_ok();
|
||||||
|
eprintln!("Migrated roles v1 to v2.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(v1) => Err(Error::msg(format!(
|
||||||
|
"failed with v2 ({}) and v1 ({})",
|
||||||
|
v2, v1
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Role represents an assignable role.
|
/// Role represents an assignable role.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct Role {
|
pub struct Role {
|
||||||
pub id: RoleId,
|
pub id: RoleId,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub reaction: Option<serenity::model::channel::ReactionType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
mod legacy {
|
||||||
|
use super::Role;
|
||||||
|
use serenity::model::id::RoleId;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use youmubot_db::{GuildMap, DB};
|
||||||
|
/// (Depreciated) A list of assignable roles for all servers.
|
||||||
|
pub type RolesV1 = DB<GuildMap<HashMap<RoleId, Role>>>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,11 @@ pub fn setup(
|
||||||
data: &mut TypeMap,
|
data: &mut TypeMap,
|
||||||
) -> 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"))?;
|
db::load_role_list(
|
||||||
|
&mut *data,
|
||||||
|
&path.join("roles_v2.yaml"),
|
||||||
|
&path.join("roles.yaml"),
|
||||||
|
)?;
|
||||||
|
|
||||||
// Create handler threads
|
// Create handler threads
|
||||||
tokio::spawn(admin::watch_soft_bans(
|
tokio::spawn(admin::watch_soft_bans(
|
||||||
|
|
|
@ -23,6 +23,11 @@ impl<T: std::any::Any + Default + Send + Sync + Clone + Serialize + std::fmt::De
|
||||||
where
|
where
|
||||||
for<'de> T: Deserialize<'de>,
|
for<'de> T: Deserialize<'de>,
|
||||||
{
|
{
|
||||||
|
/// Load the DB from a path.
|
||||||
|
pub fn load_from_path(path: impl AsRef<Path>) -> Result<Database<T>, DBError> {
|
||||||
|
Ok(Database::<T>::load_from_path(path)?)
|
||||||
|
}
|
||||||
|
|
||||||
/// Insert into a ShareMap.
|
/// Insert into a ShareMap.
|
||||||
pub fn insert_into(data: &mut TypeMap, path: impl AsRef<Path>) -> Result<(), DBError> {
|
pub fn insert_into(data: &mut TypeMap, path: impl AsRef<Path>) -> Result<(), DBError> {
|
||||||
let db = Database::<T>::load_from_path_or_default(path)?;
|
let db = Database::<T>::load_from_path_or_default(path)?;
|
||||||
|
|
Loading…
Add table
Reference in a new issue