Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: role levels #1

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions app/src/commands/levels.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use crate::{Context, Error};
use poise::serenity_prelude::{GuildChannel, Role};
use std::vec;

pub fn level_from_xp(xp: u32) -> u32 {
((xp as f32 / 100.0).powf(0.65)).floor() as u32
}

pub fn xp_to_next_level(xp: u32) -> u32 {
let current_level = level_from_xp(xp);
let next_level = current_level + 1;
let xp_for_next_level = ((next_level as f32).powf(1.0 / 0.65)) * 100.0;
(xp_for_next_level - xp as f32).round() as u32 + 1
}

#[poise::command(
slash_command,
subcommands("add_role", "remove_role", "remove_level", "set_channel", "get_level")
)]
pub async fn levels(_ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}

#[poise::command(slash_command, required_permissions = "MANAGE_GUILD")]
pub async fn add_role(ctx: Context<'_>, role: Role, level: u32) -> Result<(), Error> {
ctx.defer_ephemeral().await?;
let mut settings = ctx.data().settings.write().await;
settings.add_level_role(role.id, level);
settings.save()?;
ctx.reply(format!("Added role `{}` for level `{}`!", role.name, level))
.await?;
Ok(())
}

#[poise::command(slash_command, required_permissions = "MANAGE_GUILD")]
pub async fn remove_role(ctx: Context<'_>, role: Role) -> Result<(), Error> {
ctx.defer_ephemeral().await?;
let mut settings = ctx.data().settings.write().await;
settings.remove_role_by_role_id(role.id);
settings.save()?;
ctx.reply(format!(
"Removed the level milestone for role `{}`!",
role.name
))
.await?;
Ok(())
}

#[poise::command(slash_command, required_permissions = "MANAGE_GUILD")]
pub async fn remove_level(ctx: Context<'_>, level: u32) -> Result<(), Error> {
ctx.defer_ephemeral().await?;
let mut settings = ctx.data().settings.write().await;
settings.remove_role_by_level(level);
settings.save()?;
ctx.reply(format!("Removed the level milestone for level {level}!"))
.await?;
Ok(())
}

#[poise::command(slash_command, required_permissions = "MANAGE_GUILD")]
pub async fn set_channel(ctx: Context<'_>, channel: GuildChannel) -> Result<(), Error> {
ctx.defer_ephemeral().await?;
let mut settings = ctx.data().settings.write().await;
settings.set_level_channel(channel.id);
settings.save()?;
ctx.reply(format!("Set channel to <#{}>!", channel.id))
.await?;
Ok(())
}

#[poise::command(slash_command)]
pub async fn get_level(ctx: Context<'_>) -> Result<(), Error> {
let mut settings = ctx.data().settings.write().await;
let Some(xp) = settings.get_xp(ctx.author().id) else {
ctx.reply("Couldn't determine your XP/level.").await?;
return Ok(());
};
let xp_left = xp_to_next_level(*xp);
let level = level_from_xp(*xp);
ctx.reply(format!(
"You are at level {level}, with {xp_left} XP left until level {}.",
level + 1
))
.await?;
Ok(())
}
5 changes: 3 additions & 2 deletions app/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod levels;
pub mod lorax;
pub mod modrinth;
pub mod network;
pub mod query;
pub mod modrinth;
pub mod lorax;
51 changes: 47 additions & 4 deletions app/src/events.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::{Data, Error};
use poise::serenity_prelude::{self as serenity, ActivityData, Interaction, OnlineStatus};
use crate::{commands, Data, Error};
use poise::serenity_prelude::{
self as serenity, ActivityData, Channel, CreateMessage, GuildChannel, Interaction,
OnlineStatus, RoleId,
};
use tracing::info;

pub async fn event_handler(
Expand All @@ -15,8 +18,6 @@ pub async fn event_handler(
Some(ActivityData::watching("our infrastructure")),
OnlineStatus::Idle,
);


}
serenity::FullEvent::InteractionCreate {
interaction: Interaction::Component(component),
Expand All @@ -33,6 +34,48 @@ pub async fn event_handler(
}
}
}
serenity::FullEvent::Message { new_message } => {
if new_message.author.bot {
return Ok(());
};
let mut settings = _data.settings.write().await;
let old_xp = *settings.get_xp(new_message.author.id).unwrap_or(&0);
let new_xp = old_xp + 25;
settings.add_xp(new_message.author.id, 25);
settings.save()?;
let old_level = commands::levels::level_from_xp(old_xp);
let new_level = commands::levels::level_from_xp(new_xp);
if old_level < new_level {
let channel = new_message.channel(ctx).await?;
let Ok(member) = new_message.member(ctx).await else {
return Ok(());
};
let level_roles: Vec<&RoleId> = settings
.get_level_roles()
.into_iter()
.filter(|a| *a.0 <= new_level && !member.roles.contains(a.1))
.map(|a| a.1)
.collect();
// member.add_roles(ctx, level_roles);
for role in level_roles {
member.add_role(ctx, role).await?;
}
match channel {
Channel::Guild(guild_channel) => {
guild_channel
.send_message(
ctx,
CreateMessage::new().content(format!(
"<@{}> just leveled up to level {new_level}!",
new_message.author.id
)),
)
.await?;
}
_ => {}
}
};
}
_ => {}
}
Ok(())
Expand Down
17 changes: 10 additions & 7 deletions app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ use events::event_handler;
use poise::serenity_prelude as serenity;
use settings::Settings;
use std::sync::Arc;
use tokio::sync::RwLock;
use tasks::{server_deletion, TaskManager};
use tasks::stats_updater::StatsUpdaterTask;
use tasks::lorax_scheduler::LoraxSchedulerTask;
use tasks::stats_updater::StatsUpdaterTask;
use tasks::{server_deletion, TaskManager};
use tokio::sync::RwLock;

#[derive(Clone)]
pub struct Data {
Expand All @@ -38,7 +38,7 @@ async fn main() -> Result<(), Error> {
| serenity::GatewayIntents::GUILD_MEMBERS;

let settings = Arc::new(RwLock::new(Settings::load()?));

let mut task_manager = TaskManager::new();
task_manager.register_task(StatsUpdaterTask::new());
task_manager.register_task(LoraxSchedulerTask::new());
Expand All @@ -51,6 +51,7 @@ async fn main() -> Result<(), Error> {
commands::modrinth::modrinth(),
commands::query::query(),
commands::network::setup_stats(),
commands::levels::levels(),
],
event_handler: |ctx, event, framework, data| {
Box::pin(event_handler(ctx, event, framework, data))
Expand All @@ -60,11 +61,13 @@ async fn main() -> Result<(), Error> {
.setup(|ctx, _ready, framework| {
Box::pin(async move {
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
let data = Data { settings: settings.clone() };

let data = Data {
settings: settings.clone(),
};

// Run tasks after framework setup
task_manager.run_all(ctx, data.clone()).await;

Ok(data)
})
})
Expand Down
65 changes: 60 additions & 5 deletions app/src/settings.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use poise::serenity_prelude::{self as serenity, UserId};
use poise::serenity_prelude::{self as serenity, ChannelId, RoleId, UserId};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
Expand Down Expand Up @@ -70,13 +70,13 @@ pub enum LoraxState {
message_id: serenity::MessageId,
submissions: HashMap<UserId, String>,
location: String,
voting_duration: u64, // Add this field
voting_duration: u64, // Add this field
tiebreaker_duration: u64,
},
Voting {
end_time: i64,
message_id: serenity::MessageId,
thread_id: Option<serenity::ChannelId>, // Add this
thread_id: Option<serenity::ChannelId>, // Add this
options: Vec<String>,
votes: HashMap<UserId, usize>,
submissions: HashMap<UserId, String>,
Expand All @@ -86,7 +86,7 @@ pub enum LoraxState {
TieBreaker {
end_time: i64,
message_id: serenity::MessageId,
thread_id: Option<serenity::ChannelId>, // Add this
thread_id: Option<serenity::ChannelId>, // Add this
options: Vec<String>,
votes: HashMap<UserId, usize>,
location: String,
Expand All @@ -102,12 +102,30 @@ impl Default for LoraxState {
}
}

#[derive(Serialize, Deserialize, Debug)]
pub struct LevelSettings {
pub xp: HashMap<UserId, u32>,
pub roles: HashMap<u32, RoleId>,
pub channel: Option<ChannelId>,
}

impl LevelSettings {
pub fn new() -> LevelSettings {
LevelSettings {
xp: HashMap::new(),
roles: HashMap::new(),
channel: None,
}
}
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Settings {
pub guilds: HashMap<serenity::GuildId, GuildSettings>,
#[serde(skip)]
file_path: PathBuf,
pub user_settings: HashMap<UserId, UserSettings>,
pub levels_settings: LevelSettings,
}

impl Default for Settings {
Expand All @@ -116,6 +134,7 @@ impl Default for Settings {
guilds: HashMap::new(),
file_path: PathBuf::from("settings.json"),
user_settings: HashMap::new(),
levels_settings: LevelSettings::new(),
}
}
}
Expand Down Expand Up @@ -164,5 +183,41 @@ impl Settings {
pub fn set_user_settings(&mut self, user_id: UserId, settings: UserSettings) {
self.user_settings.insert(user_id, settings);
}
}

pub fn add_level_role(&mut self, role_id: RoleId, level: u32) {
self.levels_settings.roles.insert(level, role_id);
}

pub fn remove_role_by_level(&mut self, level: u32) {
self.levels_settings.roles.remove(&level);
}

pub fn get_level_roles(&mut self) -> &HashMap<u32, RoleId> {
&self.levels_settings.roles
}

pub fn remove_role_by_role_id(&mut self, role: RoleId) {
if let Some(key) = self
.levels_settings
.roles
.iter()
.find(|(_, v)| **v == role)
.map(|(k, _)| *k)
{
self.levels_settings.roles.remove(&key);
}
}

pub fn add_xp(&mut self, user_id: UserId, xp: u32) {
let existing = self.levels_settings.xp.get(&user_id).unwrap_or(&1);
self.levels_settings.xp.insert(user_id, *existing + xp);
}

pub fn set_level_channel(&mut self, channel_id: ChannelId) {
self.levels_settings.channel = Some(channel_id);
}

pub fn get_xp(&mut self, user_id: UserId) -> Option<&u32> {
self.levels_settings.xp.get(&user_id)
}
}