Skip to content

Commit f53e473

Browse files
authored
Merge pull request #240 from Rustmail/239-ping-command
feat(commands): add ping command
2 parents b39371f + 7978a8c commit f53e473

14 files changed

Lines changed: 272 additions & 7 deletions

File tree

rustmail/src/bot.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,28 @@ use crate::prelude::panel_commands::*;
77
use crate::prelude::types::*;
88
use base64::Engine;
99
use rand::RngCore;
10-
use serenity::all::{ClientBuilder, GatewayIntents};
10+
use serenity::all::{ClientBuilder, GatewayIntents, ShardManager};
1111
use serenity::cache::Settings as CacheSettings;
12+
use serenity::prelude::TypeMapKey;
1213
use std::collections::HashMap;
1314
use std::process;
1415
use std::sync::Arc;
1516
use std::time::Duration;
1617
use tokio::sync::Mutex;
1718
use tokio::{select, spawn};
1819

20+
pub struct ShardManagerKey;
21+
22+
impl TypeMapKey for ShardManagerKey {
23+
type Value = Arc<ShardManager>;
24+
}
25+
1926
pub async fn init_bot_state() -> Arc<Mutex<BotState>> {
2027
let pool = init_database().await.expect("An error occured!");
2128
println!("Database connected!");
2229

2330
let mut bytes = [0u8; 32];
24-
rand::thread_rng().fill_bytes(&mut bytes);
31+
rand::rng().fill_bytes(&mut bytes);
2532
let token = base64::engine::general_purpose::STANDARD.encode(&bytes);
2633

2734
let config = load_config("config.toml");
@@ -136,6 +143,7 @@ pub async fn run_bot(
136143
registry.register_command(LogsCommand);
137144
registry.register_command(TakeCommand);
138145
registry.register_command(ReleaseCommand);
146+
registry.register_command(PingCommand);
139147

140148
let registry = Arc::new(registry);
141149

@@ -174,6 +182,11 @@ pub async fn run_bot(
174182
state_lock.bot_http = Some(client.http.clone());
175183
}
176184

185+
{
186+
let mut data = client.data.write().await;
187+
data.insert::<ShardManagerKey>(client.shard_manager.clone());
188+
}
189+
177190
if let Err(e) = config.validate_servers(&client.http).await {
178191
eprintln!("Configuration validation error: {}", e);
179192
eprintln!(

rustmail/src/commands/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub mod id;
2222
pub mod logs;
2323
pub mod move_thread;
2424
pub mod new_thread;
25+
pub mod ping;
2526
pub mod recover;
2627
pub mod release;
2728
pub mod remove_reminder;
@@ -42,6 +43,7 @@ pub use id::*;
4243
pub use logs::*;
4344
pub use move_thread::*;
4445
pub use new_thread::*;
46+
pub use ping::*;
4547
pub use recover::*;
4648
pub use release::*;
4749
pub use remove_reminder::*;

rustmail/src/commands/ping/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
mod slash_command;
2+
mod text_command;
3+
4+
pub use slash_command::*;
5+
pub use text_command::*;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod ping;
2+
3+
pub use ping::*;
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
use crate::bot::ShardManagerKey;
2+
use crate::commands::{BoxFuture, CommunityRegistrable, RegistrableCommand};
3+
use crate::config::Config;
4+
use crate::errors::{DiscordError, ModmailError, ModmailResult};
5+
use crate::handlers::InteractionHandler;
6+
use crate::i18n::get_translated_message;
7+
use crate::utils::{MessageBuilder, defer_response};
8+
use chrono::Utc;
9+
use serenity::FutureExt;
10+
use serenity::all::{CommandInteraction, Context, CreateCommand, ResolvedOption};
11+
use std::collections::HashMap;
12+
use std::sync::Arc;
13+
use std::time::Duration;
14+
use tokio::time::Instant;
15+
16+
pub struct PingCommand;
17+
18+
#[async_trait::async_trait]
19+
impl RegistrableCommand for PingCommand {
20+
fn as_community(&self) -> Option<&dyn CommunityRegistrable> {
21+
None
22+
}
23+
24+
fn name(&self) -> &'static str {
25+
"ping"
26+
}
27+
28+
fn doc<'a>(&self, config: &'a Config) -> BoxFuture<'a, String> {
29+
async move { get_translated_message(config, "help.ping", None, None, None, None).await }
30+
.boxed()
31+
}
32+
33+
fn register(&self, config: &Config) -> BoxFuture<'_, Vec<CreateCommand>> {
34+
let config = config.clone();
35+
36+
Box::pin(async move {
37+
let cmd_desc = get_translated_message(
38+
&config,
39+
"slash_command.ping_command_desc",
40+
None,
41+
None,
42+
None,
43+
None,
44+
)
45+
.await;
46+
47+
vec![CreateCommand::new(self.name()).description(cmd_desc)]
48+
})
49+
}
50+
51+
fn run(
52+
&self,
53+
ctx: &Context,
54+
command: &CommandInteraction,
55+
_options: &[ResolvedOption<'_>],
56+
config: &Config,
57+
_handler: Arc<InteractionHandler>,
58+
) -> BoxFuture<'_, ModmailResult<()>> {
59+
let ctx = ctx.clone();
60+
let command = command.clone();
61+
let config = config.clone();
62+
63+
Box::pin(async move {
64+
defer_response(&ctx, &command).await?;
65+
let response = MessageBuilder::system_message(&ctx, &config)
66+
.content("...")
67+
.to_channel(command.channel_id)
68+
.build_interaction_message_followup()
69+
.await;
70+
71+
let shard_manager = ctx
72+
.data
73+
.read()
74+
.await
75+
.get::<ShardManagerKey>()
76+
.cloned()
77+
.ok_or(ModmailError::Discord(DiscordError::ShardManagerNotFound))?;
78+
79+
let time_before = Instant::now();
80+
let mut res = command.create_followup(&ctx.http, response).await?;
81+
let msg_send_ping = time_before.elapsed().as_millis();
82+
83+
let gateway_ping = {
84+
let runners = shard_manager.runners.lock().await;
85+
runners.get(&ctx.shard_id).and_then(|runner| runner.latency)
86+
};
87+
88+
let start = Instant::now();
89+
ctx.http.get_gateway().await?;
90+
let api_ping = start.elapsed();
91+
92+
let mut params = HashMap::new();
93+
params.insert(
94+
"gateway_latency".to_string(),
95+
format!(
96+
"{:?}",
97+
gateway_ping.unwrap_or(Duration::default()).as_millis()
98+
),
99+
);
100+
params.insert(
101+
"api_latency".to_string(),
102+
format!("{:?}", api_ping.as_millis()),
103+
);
104+
params.insert(
105+
"message_latency".to_string(),
106+
format!("{:?}", msg_send_ping),
107+
);
108+
109+
let response = MessageBuilder::system_message(&ctx, &config)
110+
.translated_content("slash_command.ping_command", Some(&params), None, None)
111+
.await
112+
.to_channel(command.channel_id)
113+
.build_edit_message()
114+
.await;
115+
116+
res.edit(&ctx.http, response).await?;
117+
118+
Ok(())
119+
})
120+
}
121+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod ping;
2+
3+
pub use ping::*;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use crate::bot::ShardManagerKey;
2+
use crate::prelude::config::*;
3+
use crate::prelude::errors::*;
4+
use crate::prelude::handlers::*;
5+
use crate::utils::MessageBuilder;
6+
use chrono::Utc;
7+
use serenity::all::{Context, Message};
8+
use std::collections::HashMap;
9+
use std::sync::Arc;
10+
use std::time::Duration;
11+
use tokio::time::Instant;
12+
13+
pub async fn ping(
14+
ctx: Context,
15+
msg: Message,
16+
config: &Config,
17+
_handler: Arc<GuildMessagesHandler>,
18+
) -> ModmailResult<()> {
19+
let shard_manager = ctx
20+
.data
21+
.read()
22+
.await
23+
.get::<ShardManagerKey>()
24+
.cloned()
25+
.ok_or(ModmailError::Discord(DiscordError::ShardManagerNotFound))?;
26+
27+
let time_before = Instant::now();
28+
let mut res = msg.reply(&ctx.http, "...").await?;
29+
let msg_send_ping = time_before.elapsed().as_millis();
30+
31+
let gateway_ping = {
32+
let runners = shard_manager.runners.lock().await;
33+
runners.get(&ctx.shard_id).and_then(|runner| runner.latency)
34+
};
35+
36+
let start = Instant::now();
37+
ctx.http.get_gateway().await?;
38+
let api_ping = start.elapsed();
39+
40+
let mut params = HashMap::new();
41+
params.insert(
42+
"gateway_latency".to_string(),
43+
format!(
44+
"{:?}",
45+
gateway_ping.unwrap_or(Duration::default()).as_millis()
46+
),
47+
);
48+
params.insert(
49+
"api_latency".to_string(),
50+
format!("{:?}", api_ping.as_millis()),
51+
);
52+
params.insert(
53+
"message_latency".to_string(),
54+
format!("{:?}", msg_send_ping),
55+
);
56+
57+
let edited_msg = MessageBuilder::system_message(&ctx, &config)
58+
.translated_content("slash_command.ping_command", Some(&params), None, None)
59+
.await
60+
.to_channel(msg.channel_id)
61+
.build_edit_message()
62+
.await;
63+
64+
res.edit(&ctx.http, edited_msg).await?;
65+
66+
Ok(())
67+
}

rustmail/src/config.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ pub struct Config {
2121

2222
pub db_pool: Option<SqlitePool>,
2323
pub error_handler: Option<Arc<ErrorHandler>>,
24-
pub thread_locks: Arc<std::sync::Mutex<std::collections::HashMap<u64, Arc<tokio::sync::Mutex<()>>>>>,
24+
pub thread_locks:
25+
Arc<std::sync::Mutex<std::collections::HashMap<u64, Arc<tokio::sync::Mutex<()>>>>>,
2526
}
2627

2728
fn get_local_ip() -> Option<String> {
@@ -50,21 +51,30 @@ pub fn load_config(path: &str) -> Option<Config> {
5051
}
5152

5253
if u64::from_str_radix(&config_response.thread.user_message_color, 16).is_err() {
53-
eprintln!("Incorrect user message color in the config.toml! Please put a color in hex format!");
54+
eprintln!(
55+
"Incorrect user message color in the config.toml! Please put a color in hex format!"
56+
);
5457
return None;
5558
}
5659

5760
if u64::from_str_radix(&config_response.thread.staff_message_color, 16).is_err() {
58-
eprintln!("Incorrect staff message color in the config.toml! Please put a color in hex format!");
61+
eprintln!(
62+
"Incorrect staff message color in the config.toml! Please put a color in hex format!"
63+
);
5964
return None;
6065
}
6166

6267
if u64::from_str_radix(&config_response.reminders.embed_color, 16).is_err() {
63-
eprintln!("Incorrect reminder embed color in the config.toml! Please put a color in hex format!");
68+
eprintln!(
69+
"Incorrect reminder embed color in the config.toml! Please put a color in hex format!"
70+
);
6471
return None;
6572
}
6673

67-
if !config_response.language.is_language_supported(config_response.language.get_default_language()) {
74+
if !config_response
75+
.language
76+
.is_language_supported(config_response.language.get_default_language())
77+
{
6878
eprintln!(
6979
"Warning: Default language '{}' is not in supported languages list",
7080
config_response.language.default_language

rustmail/src/errors/dictionary.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ impl DictionaryManager {
188188
DiscordError::UserIsABot => ("discord.user_is_a_bot".to_string(), None),
189189
DiscordError::PermissionDenied => ("discord.permission_denied".to_string(), None),
190190
DiscordError::DmCreationFailed => ("discord.dm_creation_failed".to_string(), None),
191+
DiscordError::ShardManagerNotFound => {
192+
("discord.shard_manager_not_found".to_string(), None)
193+
}
191194
_ => ("discord.api_error".to_string(), None),
192195
},
193196
ModmailError::Command(cmd_err) => match cmd_err {

rustmail/src/errors/types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ pub enum DiscordError {
5656
DmCreationFailed,
5757
FailedToFetchCategories,
5858
FailedToMoveChannel,
59+
ShardManagerNotFound,
5960
}
6061

6162
#[derive(Debug, Clone)]
@@ -198,6 +199,7 @@ impl fmt::Display for DiscordError {
198199
DiscordError::DmCreationFailed => write!(f, "Failed to create DM channel"),
199200
DiscordError::FailedToFetchCategories => write!(f, "Failed to fetch categories"),
200201
DiscordError::FailedToMoveChannel => write!(f, "Failed to move_thread channel"),
202+
DiscordError::ShardManagerNotFound => write!(f, "Shard manager not found"),
201203
}
202204
}
203205
}

0 commit comments

Comments
 (0)