Skip to content

Commit

Permalink
wip: delay ready role removal
Browse files Browse the repository at this point in the history
  • Loading branch information
imatpot committed Aug 31, 2024
1 parent fd8c84b commit 6b7103d
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 42 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ dotenv = { version = "0.15.0" }
futures = { version = "0.3.30" }
log = { version = "0.4.22" }
log4rs = { version = "1.3.0" }
poise = { version = "0.6.1" }
poise = { version = "0.6.0" }
regex = { version = "1.10.6" }
reqwest = { version = "0.12.7" }
rustc_version_runtime = { version = "0.3.0" }
serde = { version = "1.0.209" }
serde_json = { version = "1.0.127" }
thiserror = { version = "1.0.63" }
tokio = { version = "1.40.0", features = ["rt-multi-thread"] }
tokio-util = { version = "0.7.11" }
toml = { version = "0.8.19" }
uuid = { version = "1.10.0", features = ["v4"] }
4 changes: 2 additions & 2 deletions src/commands/about.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ struct Committer {
pub async fn run(context: PoiseContext<'_>) -> Result<(), Error> {
let env = Environment::instance();

let tracking_config = lunaro_tracking::Config::instance().await;
let tracking_config = lunaro_tracking::LunaroTrackingConfig::instance().await;
let member_count = context
.partial_guild()
.await
Expand Down Expand Up @@ -99,7 +99,7 @@ pub async fn run(context: PoiseContext<'_>) -> Result<(), Error> {
format!("⚙️ [Serenity](<https://github.com/serenity-rs/serenity>) {serenity_version} / [Poise](<https://github.com/serenity-rs/poise>) {poise_version}"),
format!("🚧 [Last updated](<{commit_url}>) {commit_date}",),
String::new(),
format!("⏱️ Started online {uptime}"),
format!("⏱️ Instance went online {uptime}"),
]
.join("\n"),
),
Expand Down
31 changes: 17 additions & 14 deletions src/events/mod.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
use poise::serenity_prelude::{async_trait, Context, Event, RawEventHandler};
use poise::{
serenity_prelude::{Context, FullEvent},
FrameworkContext,
};

use crate::types::{error::Error, poise::PoiseData};

mod presence_update;
mod ready;

pub struct EventHandlers;

#[async_trait]
impl RawEventHandler for EventHandlers {
async fn raw_event(&self, context: Context, raw_event: Event) {
let handler = match &raw_event {
Event::Ready(event) => ready::handle(context, &event.ready).await,
Event::PresenceUpdate(event) => presence_update::handle(context, &event.presence).await,
_ => Ok(()),
};
pub async fn event_handler(
context: &Context,
event: &FullEvent,
_framework: FrameworkContext<'_, PoiseData, Error>,
_data: &PoiseData,
) -> Result<(), Error> {
log::debug!("Received event: {:#?}", event);

if let Err(error) = handler {
log::warn!("Error while handling {:?}: {error:?}: {error}", raw_event);
}
match event {
FullEvent::Ready { data_about_bot } => ready::handle(context, data_about_bot).await,
FullEvent::PresenceUpdate { new_data } => presence_update::handle(context, new_data).await,
_ => Ok(()),
}
}
107 changes: 98 additions & 9 deletions src/events/presence_update.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use poise::serenity_prelude::{Context, Presence};
use std::time::Duration;

use poise::serenity_prelude::{prelude::TypeMapKey, Context, Member, Presence, UserId};
use tokio::time::sleep;
use tokio_util::sync::CancellationToken;

use crate::{
env::Environment,
Expand All @@ -7,10 +11,24 @@ use crate::{
util::{lunaro_tracking, play},
};

struct ScheduledReadyRemovals {
pub scheduled: Vec<(UserId, CancellationToken)>,
}

impl Default for ScheduledReadyRemovals {
fn default() -> Self {
Self {
scheduled: Vec::new(),
}
}
}

/// Handles the presence update event.
pub async fn handle(context: Context, presence: &Presence) -> Result<(), Error> {
pub async fn handle(context: &Context, presence: &Presence) -> Result<(), Error> {
log::debug!("Presence update");

let env = Environment::instance();
let tracking_config = lunaro_tracking::Config::instance().await;
let tracking_config = lunaro_tracking::LunaroTrackingConfig::instance().await;

if tracking_config.is_blocked(&presence.user.id) {
return Ok(());
Expand All @@ -21,18 +39,89 @@ pub async fn handle(context: Context, presence: &Presence) -> Result<(), Error>
.get_member(presence.guild_id.unwrap(), presence.user.id)
.await?;

let is_playing = member
let has_playing_role = member
.user
.has_role(&context, env.home_guild_id, env.playing_role_id)
.await?;

let is_playing_lunaro = lunaro_tracking::is_playing_lunaro(presence).is_ok_and(|value| value);
let is_in_game = lunaro_tracking::is_playing_lunaro(presence).is_ok_and(|value| value);

if is_playing_lunaro && !is_playing {
play::add(member, &context).await?;
} else if !is_playing_lunaro && is_playing {
play::remove(member, &context).await?;
if is_in_game && !has_playing_role {
add_ready_role(member, &context).await?;
} else if !is_in_game && has_playing_role {
schedule_ready_removal(member.clone(), &context).await;
}

Ok(())
}

async fn add_ready_role(member: &mut Member, context: &Context) -> Result<(), Error> {
let cancellation_schedules = context
.data
.read()
.await
.get::<ScheduledReadyRemovals>()
.unwrap_or(&ScheduledReadyRemovals::default())
.scheduled
.clone();

if !cancellation_schedules.is_empty() {
let cancellation_token = cancellation_schedules
.iter()
.find(|(user_id, _)| user_id == &member.user.id)
.map(|(_, token)| token.clone());

if let Some(token) = cancellation_token {
token.cancel();
}

let uncancelled_tokens = cancellation_schedules
.iter()
.filter(|(_, token)| !token.is_cancelled())
.map(|(user_id, token)| (*user_id, token.clone()))
.collect::<Vec<_>>();

context
.data
.write()
.await
.get_mut::<ScheduledReadyRemovals>()
.unwrap()
.scheduled = uncancelled_tokens;
}

play::add(member, context).await
}

async fn schedule_ready_removal(mut member: Member, context: &Context) {
let cancellation_token = CancellationToken::new();
let entry = (member.user.id, cancellation_token.clone());

context
.data
.write()
.await
.get_mut::<ScheduledReadyRemovals>()
.unwrap()
.scheduled
.push(entry);

let context = context.clone();

tokio::spawn(async move {
tokio::select! {
_ = cancellation_token.cancelled() => {
log::info!("Cancelling ready removal for {} ({})", member.user.tag(), member.user.id);
}
_ = sleep(Duration::from_secs(5)) => {
play::remove(&mut member, &context).await.unwrap_or_else(|error| {
log::error!("Failed to remove ready role for {} ({}): {}", member.user.tag(), member.user.id, error);
});
}
}
});
}

impl TypeMapKey for ScheduledReadyRemovals {
type Value = Self;
}
11 changes: 9 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ async fn main() {
let framework_options = FrameworkOptions {
pre_command: |context| Box::pin(log_invocation(context)),
on_error: |error| Box::pin(on_framework_error(error)),
event_handler: |context, event, framework, data| {
Box::pin(events::event_handler(context, event, framework, data))
},


commands: vec![
Expand Down Expand Up @@ -87,7 +90,9 @@ async fn main() {
/// Logs command invocations.
async fn log_invocation(context: PoiseContext<'_>) {
let author = &context.author().tag();
let author_id = &context.author().id;
let guild = &context.partial_guild().await.unwrap().name;
let guild_id = &context.guild_id().unwrap();
let parent_commands = context
.parent_commands()
.iter()
Expand All @@ -102,7 +107,7 @@ async fn log_invocation(context: PoiseContext<'_>) {
format!("{} {}", parent_commands, context.command().name)
};

log::info!("{author} ran [{command}] in {guild}");
log::info!("{author} ({author_id}) ran [{command}] in {guild} ({guild_id})");
}

/// Handles framework errors according to their severity.
Expand All @@ -125,12 +130,14 @@ async fn on_framework_error(error: FrameworkError<'_, PoiseData, Error>) {
/// Logs the error and notifies the affected guild.
async fn log_error(message: &str, context: PoiseContext<'_>) {
let user = &context.author().tag();
let user_id = &context.author().id;
let command = &context.command().name;
let guild = &context.partial_guild().await.unwrap().name;
let guild_id = &context.guild_id().unwrap();

let trace_id = Uuid::new_v4();

log::error!("{user} ran [{command}] in {guild} and got an error: {message} ({trace_id})",);
log::error!("{user} ({user_id}) ran [{command}] in {guild} ({guild_id}) and got an error: {message} ({trace_id})",);

send_error_to_chat(message, trace_id, context).await;
}
Expand Down
16 changes: 8 additions & 8 deletions src/util/lunaro_tracking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ use crate::{errors::data::DataError, traits::config_file::ConfigFile, types::err

use super::data;

static CONFIG: OnceLock<Mutex<Config>> = OnceLock::new();
static CONFIG: OnceLock<Mutex<LunaroTrackingConfig>> = OnceLock::new();
const CONFIG_FILE: &str = "lunaro_tracking.json";

/// Configures the Lunaro tracker's behaviour.
#[derive(Serialize, Deserialize, Default, Debug)]
pub struct Config {
pub struct LunaroTrackingConfig {
/// List of user IDs to ignore Lunaro updates from.
pub blocklist: HashSet<UserId>,
}

impl Config {
impl LunaroTrackingConfig {
/// Check if a user is on the tracking blocklist.
pub fn is_blocked(&self, user_id: &UserId) -> bool {
self.blocklist.contains(user_id)
Expand All @@ -39,13 +39,13 @@ impl Config {
}

#[async_trait]
impl ConfigFile for Config {
impl ConfigFile for LunaroTrackingConfig {
fn load() -> Result<Box<Self>, Error> {
match data::read_config(CONFIG_FILE) {
Ok(config) => Ok(config),
Err(error) => match error.downcast_ref::<DataError>() {
Some(DataError::MissingConfigFile(_)) => {
let config = Config::default();
let config = LunaroTrackingConfig::default();
config.save()?;
Ok(config.into())
}
Expand All @@ -60,15 +60,15 @@ impl ConfigFile for Config {

async fn instance<'a>() -> MutexGuard<'a, Self> {
CONFIG
.get_or_init(|| Mutex::new(*Config::load().unwrap_or_default()))
.get_or_init(|| Mutex::new(*LunaroTrackingConfig::load().unwrap_or_default()))
.lock()
.await
}
}

/// Remove a user from the tracking blocklist, if present.
pub async fn allow_for(user: &User) -> Result<(), Error> {
let mut config = Config::instance().await;
let mut config = LunaroTrackingConfig::instance().await;

config.remove_from_blocklist(&user.id)?;

Expand All @@ -83,7 +83,7 @@ pub async fn allow_for(user: &User) -> Result<(), Error> {

/// Add a user to the tracking blocklist.
pub async fn deny_for(user: &User) -> Result<(), Error> {
let mut config = Config::instance().await;
let mut config = LunaroTrackingConfig::instance().await;

config.add_to_blocklist(&user.id)?;

Expand Down
12 changes: 10 additions & 2 deletions src/util/play.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ pub async fn add(member: &mut Member, context: &Context) -> Result<(), Error> {
.add_role(context, Environment::instance().playing_role_id)
.await?;

log::debug!("Added {} to playing role", member.user.tag());
log::debug!(
"Added {} ({}) to playing role",
member.user.tag(),
member.user.id
);

Ok(())
}
Expand All @@ -41,7 +45,11 @@ pub async fn remove(member: &mut Member, context: &Context) -> Result<(), Error>
.remove_role(context, Environment::instance().playing_role_id)
.await?;

log::debug!("Removed {} from playing role", member.user.tag());
log::debug!(
"Removed {} ({}) from playing role",
member.user.tag(),
member.user.id
);

Ok(())
}

0 comments on commit 6b7103d

Please sign in to comment.