use crate::serenity_prelude as serenity;
use std::collections::HashMap;
use std::time::{Duration, Instant};
#[derive(Default, Clone, PartialEq, Eq, Debug, Hash)]
pub struct CooldownContext {
pub user_id: serenity::UserId,
pub guild_id: Option<serenity::GuildId>,
pub channel_id: serenity::ChannelId,
}
#[derive(Default, Clone, PartialEq, Eq, Debug, Hash)]
pub struct CooldownConfig {
pub global: Option<Duration>,
pub user: Option<Duration>,
pub guild: Option<Duration>,
pub channel: Option<Duration>,
pub member: Option<Duration>,
#[doc(hidden)]
pub __non_exhaustive: (),
}
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct CooldownTracker {
global_invocation: Option<Instant>,
user_invocations: HashMap<serenity::UserId, Instant>,
guild_invocations: HashMap<serenity::GuildId, Instant>,
channel_invocations: HashMap<serenity::ChannelId, Instant>,
member_invocations: HashMap<(serenity::UserId, serenity::GuildId), Instant>,
}
#[non_exhaustive]
pub enum CooldownType {
Global,
User(serenity::UserId),
Guild(serenity::GuildId),
Channel(serenity::ChannelId),
Member((serenity::UserId, serenity::GuildId)),
}
pub use CooldownTracker as Cooldowns;
impl CooldownTracker {
pub fn new() -> Self {
Self {
global_invocation: None,
user_invocations: HashMap::new(),
guild_invocations: HashMap::new(),
channel_invocations: HashMap::new(),
member_invocations: HashMap::new(),
}
}
pub fn remaining_cooldown(
&self,
ctx: CooldownContext,
cooldown_durations: &CooldownConfig,
) -> Option<Duration> {
let mut cooldown_data = vec![
(cooldown_durations.global, self.global_invocation),
(
cooldown_durations.user,
self.user_invocations.get(&ctx.user_id).copied(),
),
(
cooldown_durations.channel,
self.channel_invocations.get(&ctx.channel_id).copied(),
),
];
if let Some(guild_id) = ctx.guild_id {
cooldown_data.push((
cooldown_durations.guild,
self.guild_invocations.get(&guild_id).copied(),
));
cooldown_data.push((
cooldown_durations.member,
self.member_invocations
.get(&(ctx.user_id, guild_id))
.copied(),
));
}
cooldown_data
.iter()
.filter_map(|&(cooldown, last_invocation)| {
let duration_since = Instant::now().saturating_duration_since(last_invocation?);
let cooldown_left = cooldown?.checked_sub(duration_since)?;
Some(cooldown_left)
})
.max()
}
pub fn start_cooldown(&mut self, ctx: CooldownContext) {
let now = Instant::now();
self.global_invocation = Some(now);
self.user_invocations.insert(ctx.user_id, now);
self.channel_invocations.insert(ctx.channel_id, now);
if let Some(guild_id) = ctx.guild_id {
self.guild_invocations.insert(guild_id, now);
self.member_invocations.insert((ctx.user_id, guild_id), now);
}
}
pub fn set_last_invocation(&mut self, cooldown_type: CooldownType, instant: Instant) {
match cooldown_type {
CooldownType::Global => self.global_invocation = Some(instant),
CooldownType::User(user_id) => {
self.user_invocations.insert(user_id, instant);
}
CooldownType::Guild(guild_id) => {
self.guild_invocations.insert(guild_id, instant);
}
CooldownType::Channel(channel_id) => {
self.channel_invocations.insert(channel_id, instant);
}
CooldownType::Member(member) => {
self.member_invocations.insert(member, instant);
}
}
}
}
impl<'a> From<&'a serenity::Message> for CooldownContext {
fn from(message: &'a serenity::Message) -> Self {
Self {
user_id: message.author.id,
channel_id: message.channel_id,
guild_id: message.guild_id,
}
}
}