Skip to content

Commit

Permalink
feat: add automatic clan wars soldiers role management
Browse files Browse the repository at this point in the history
  • Loading branch information
motzel committed Mar 1, 2024
1 parent 2eff2d6 commit 73f24a8
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 34 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bl-bot"
version = "0.17.0"
version = "0.17.1"
description = "Beat Leader Discord Bot"
readme = "README.md"
repository = "https://github.com/motzel/bl-bot"
Expand Down
45 changes: 44 additions & 1 deletion src/discord/bot/commands/clan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::discord::bot::commands::player::{
use crate::discord::bot::{ClanSettings, GuildOAuthTokenRepository};
use crate::discord::Context;
use crate::{Error, BL_CLIENT};
use poise::serenity_prelude::{ChannelId, CreateAttachment, Message, Permissions, User};
use poise::serenity_prelude::{ChannelId, CreateAttachment, Message, Permissions, Role, User};

/// Set up sending of clan invitations
#[tracing::instrument(skip(ctx), level=tracing::Level::INFO, name="bot_command:bl-set-clan-invitation")]
Expand Down Expand Up @@ -924,3 +924,46 @@ pub(crate) async fn cmd_clan_wars_release(
}
}
}

/// Set soldier role for clan wars
#[tracing::instrument(skip(ctx), level=tracing::Level::INFO, name="bot_command:bl-set-clan-wars-soldier-role")]
#[poise::command(
slash_command,
rename = "bl-set-clan-wars-soldier-role",
ephemeral,
required_permissions = "MANAGE_ROLES",
default_member_permissions = "MANAGE_ROLES",
required_bot_permissions = "MANAGE_ROLES",
guild_only,
hide_in_help
)]
pub(crate) async fn cmd_set_clan_wars_soldier_role(
ctx: Context<'_>,
#[description = "Role to assign. Leve empty to remove."] role: Option<Role>,
) -> Result<(), Error> {
let guild_settings = get_guild_settings(ctx, true).await?;
if guild_settings.clan_settings.is_none() {
say_without_ping(ctx, "Clan is not set up in this guild.", true).await?;

return Ok(());
}

match ctx
.data()
.guild_settings_repository
.set_clan_wars_soldier_role(&guild_settings.guild_id, role.map(|r| r.id))
.await
{
Ok(guild_settings) => {
say_without_ping(ctx, format!("{}", guild_settings).as_str(), true).await?;

Ok(())
}
Err(e) => {
let message = format!("An error occurred: {}", e);
say_without_ping(ctx, message.as_str(), true).await?;

Ok(())
}
}
}
2 changes: 2 additions & 0 deletions src/discord/bot/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::discord::bot::commands::clan::{
cmd_capture, cmd_clan_wars_enlist, cmd_clan_wars_playlist, cmd_clan_wars_release,
cmd_set_clan_wars_contribution_channel, cmd_set_clan_wars_maps_channel,
cmd_set_clan_wars_soldier_role,
};
use crate::discord::{BotData, Context};
pub(crate) use backup::{cmd_export, cmd_import};
Expand Down Expand Up @@ -37,6 +38,7 @@ pub(crate) fn commands() -> Vec<poise::Command<BotData, crate::Error>> {
cmd_set_clan_wars_contribution_channel(),
cmd_clan_wars_enlist(),
cmd_clan_wars_release(),
cmd_set_clan_wars_soldier_role(),
cmd_capture(),
// cmd_invite_player(),
cmd_register(),
Expand Down
107 changes: 83 additions & 24 deletions src/discord/bot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,12 @@ impl GuildSettings {
}
}

pub fn set_clan_wars_soldier_role(&mut self, role_id: Option<RoleId>) {
if let Some(ref mut clan_settings) = self.clan_settings {
clan_settings.set_clan_wars_soldier_role(role_id);
}
}

pub fn get_clan_settings(&self) -> Option<ClanSettings> {
self.clan_settings.clone()
}
Expand Down Expand Up @@ -942,7 +948,6 @@ impl GuildSettings {

pub(crate) fn get_role_updates(
&self,
guild_id: GuildId,
player: &Player,
current_roles: &[RoleId],
) -> UserRoleChanges {
Expand All @@ -955,7 +960,8 @@ impl GuildSettings {

let mut ru = UserRoleStatus::default();

self.role_groups
let mut auto_role_changes = self
.role_groups
.values()
.map(|roles| {
let mut roles_fulfillment = roles
Expand Down Expand Up @@ -990,7 +996,63 @@ impl GuildSettings {

acc
})
.get_role_changes(guild_id, player, current_roles)
.get_role_changes(self.guild_id, player, current_roles);

let soldier_role_changes = self.get_soldier_role_changes(player, current_roles);
if !soldier_role_changes.to_add.is_empty() || !soldier_role_changes.to_remove.is_empty() {
auto_role_changes.to_add.extend(soldier_role_changes.to_add);
auto_role_changes
.to_remove
.extend(soldier_role_changes.to_remove);
}

auto_role_changes
}

pub(crate) fn get_soldier_role_changes(
&self,
player: &Player,
current_roles: &[RoleId],
) -> UserRoleChanges {
if self.clan_settings.is_none()
|| self.clan_settings.as_ref().unwrap().soldier_role.is_none()
{
return UserRoleChanges {
guild_id: self.guild_id,
user_id: player.user_id,
name: player.name.clone(),
to_add: Vec::new(),
to_remove: Vec::new(),
};
}

let clan_settings = self.clan_settings.as_ref().unwrap();
let soldier_role = clan_settings.soldier_role.unwrap();

let is_enlisted = clan_settings
.get_clan_wars_soldiers()
.iter()
.any(|s| s == &player.user_id);

let is_clan_member =
!player.clans.is_empty() && player.clans.first().unwrap() == &clan_settings.clan;

UserRoleChanges {
guild_id: self.guild_id,
user_id: player.user_id,
name: player.name.clone(),
to_add: if !current_roles.contains(&soldier_role) && is_enlisted && is_clan_member {
vec![soldier_role]
} else {
Vec::new()
},
to_remove: if current_roles.contains(&soldier_role) && (!is_enlisted || !is_clan_member)
{
vec![soldier_role]
} else {
Vec::new()
},
}
}
}

Expand Down Expand Up @@ -1058,6 +1120,7 @@ pub struct ClanSettings {
clan_wars_maps_posted_at: Option<DateTime<Utc>>,
clan_wars_contribution_channel_id: Option<ChannelId>,
clan_wars_contribution_posted_at: Option<DateTime<Utc>>,
soldier_role: Option<RoleId>,
soldiers: Vec<UserId>,
}

Expand All @@ -1080,6 +1143,7 @@ impl ClanSettings {
clan_wars_contribution_channel_id: None,
clan_wars_maps_posted_at: None,
clan_wars_contribution_posted_at: None,
soldier_role: None,
soldiers: Vec::new(),
}
}
Expand Down Expand Up @@ -1150,6 +1214,10 @@ impl ClanSettings {
}
}

pub fn set_clan_wars_soldier_role(&mut self, role_id: Option<RoleId>) {
self.soldier_role = role_id;
}

pub fn get_clan_wars_soldiers(&self) -> &Vec<UserId> {
&self.soldiers
}
Expand All @@ -1160,7 +1228,7 @@ impl std::fmt::Display for ClanSettings {
if self.oauth_token_is_set {
write!(
f,
"Set up for the clan {}. Users can{} send themselves invitations.\nClan wars maps channel: {}\nClan wars contribution channel: {}",
"Set up for the clan {}. Users can{} send themselves invitations.\nClan wars maps channel: {}\nClan wars contribution channel: {}\nClan wars soldier role: {}",
self.clan,
if !self.supports_self_invitation() {
" NOT"
Expand All @@ -1169,11 +1237,15 @@ impl std::fmt::Display for ClanSettings {
},
self.clan_wars_maps_channel_id.map_or_else(
|| "**None**".to_owned(),
|channel_id| format!("<#{}>", channel_id.to_owned())
|channel_id| format!("<#{}>", channel_id)
),
self.clan_wars_contribution_channel_id.map_or_else(
|| "**None**".to_owned(),
|channel_id| format!("<#{}>", channel_id.to_owned())
|channel_id| format!("<#{}>", channel_id)
),
self.soldier_role.map_or_else(
|| "**None**".to_owned(),
|role_id| format!("<@&{}>", role_id)
),
)
} else {
Expand Down Expand Up @@ -1745,7 +1817,6 @@ mod tests {
};

let mut roles_updates = gs.get_role_updates(
GuildId::new(1),
&player,
&vec![RoleId::new(1), RoleId::new(3), RoleId::new(7)],
);
Expand All @@ -1761,8 +1832,7 @@ mod tests {

player.top_accuracy = 89.0;

let mut roles_updates =
gs.get_role_updates(GuildId::new(1), &player, &vec![RoleId::new(1)]);
let mut roles_updates = gs.get_role_updates(&player, &vec![RoleId::new(1)]);

roles_updates.to_add.sort_unstable();
roles_updates.to_remove.sort_unstable();
Expand All @@ -1772,7 +1842,7 @@ mod tests {

player.pp = 10000.0;

let mut roles_updates = gs.get_role_updates(GuildId::new(1), &player, &vec![]);
let mut roles_updates = gs.get_role_updates(&player, &vec![]);

roles_updates.to_add.sort_unstable();
roles_updates.to_remove.sort_unstable();
Expand All @@ -1782,8 +1852,7 @@ mod tests {

player.rank = 1000;

let mut roles_updates =
gs.get_role_updates(GuildId::new(1), &player, &vec![RoleId::new(2)]);
let mut roles_updates = gs.get_role_updates(&player, &vec![RoleId::new(2)]);

roles_updates.to_add.sort_unstable();
roles_updates.to_remove.sort_unstable();
Expand All @@ -1793,11 +1862,7 @@ mod tests {

player.rank = 500;

let mut roles_updates = gs.get_role_updates(
GuildId::new(1),
&player,
&vec![RoleId::new(2), RoleId::new(3)],
);
let mut roles_updates = gs.get_role_updates(&player, &vec![RoleId::new(2), RoleId::new(3)]);

roles_updates.to_add.sort_unstable();
roles_updates.to_remove.sort_unstable();
Expand All @@ -1807,11 +1872,7 @@ mod tests {

player.clans = vec!["Clan1".to_string()];

let mut roles_updates = gs.get_role_updates(
GuildId::new(1),
&player,
&vec![RoleId::new(2), RoleId::new(3)],
);
let mut roles_updates = gs.get_role_updates(&player, &vec![RoleId::new(2), RoleId::new(3)]);

roles_updates.to_add.sort_unstable();
roles_updates.to_remove.sort_unstable();
Expand All @@ -1822,7 +1883,6 @@ mod tests {
player.clans = vec!["Other".to_string()];

let mut roles_updates = gs.get_role_updates(
GuildId::new(1),
&player,
&vec![RoleId::new(2), RoleId::new(3), RoleId::new(6)],
);
Expand All @@ -1839,7 +1899,6 @@ mod tests {
player.last_ranked_paused_at = Some(Utc::now() - Duration::days(50));

let mut roles_updates = gs.get_role_updates(
GuildId::new(1),
&player,
&vec![RoleId::new(2), RoleId::new(3), RoleId::new(6)],
);
Expand Down
2 changes: 1 addition & 1 deletion src/discord/worker/user_roles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ impl UserRolesWorker {
.filter_map(|(guild_id, player, roles)| {
guilds
.get(guild_id)
.map(|guild_settings| guild_settings.get_role_updates(*guild_id, player, roles))
.map(|guild_settings| guild_settings.get_role_updates(player, roles))
})
.collect::<Vec<UserRoleChanges>>();

Expand Down
40 changes: 34 additions & 6 deletions src/storage/guild.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,12 +236,7 @@ impl<'a> GuildSettingsRepository {
.get_and_modify_or_insert(
guild_id,
|guild_settings| guild_settings.add_clan_wars_soldier(user_id),
|| {
let mut guild_settings = GuildSettings::new(*guild_id);
guild_settings.add_clan_wars_soldier(user_id);

Some(guild_settings)
},
|| Some(GuildSettings::new(*guild_id)),
)
.await?
{
Expand Down Expand Up @@ -291,6 +286,39 @@ impl<'a> GuildSettingsRepository {
}
}

pub(crate) async fn set_clan_wars_soldier_role(
&self,
guild_id: &GuildId,
role_id: Option<RoleId>,
) -> Result<GuildSettings> {
trace!(
"Setting new clan wars soldier role {:?} for guild {}...",
role_id,
guild_id
);

if let Some(guild_settings) = self
.storage
.get_and_modify_or_insert(
guild_id,
|guild_settings| guild_settings.set_clan_wars_soldier_role(role_id),
|| Some(GuildSettings::new(*guild_id)),
)
.await?
{
debug!(
"Clan wars soldier role {:?} for guild {} set.",
role_id, guild_id
);

Ok(guild_settings)
} else {
Err(StorageError::NotFound(
"guild is not registered".to_string(),
))
}
}

pub(crate) async fn set_verified_profile_requirement(
&self,
guild_id: &GuildId,
Expand Down

0 comments on commit 73f24a8

Please sign in to comment.