Skip to content

Commit d06d66d

Browse files
authored
Merge pull request #271 from Rustmail/267-rework-snippet-command
refactor(snippet): implement 'use' subcommand to be more user-friendly
2 parents 164c8ee + 27af2b0 commit d06d66d

5 files changed

Lines changed: 216 additions & 28 deletions

File tree

rustmail/src/commands/snippet/slash_command/snippet.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@ impl RegistrableCommand for SnippetCommand {
9595
None,
9696
)
9797
.await;
98+
let use_desc = get_translated_message(
99+
&config,
100+
"slash_command.snippet_use_description",
101+
None,
102+
None,
103+
None,
104+
None,
105+
)
106+
.await;
98107
let key_desc = get_translated_message(
99108
&config,
100109
"slash_command.snippet_key_argument",
@@ -181,6 +190,17 @@ impl RegistrableCommand for SnippetCommand {
181190
"delete",
182191
delete_desc,
183192
)
193+
.add_sub_option(
194+
CreateCommandOption::new(CommandOptionType::String, "key", key_desc.clone())
195+
.required(true),
196+
),
197+
)
198+
.add_option(
199+
CreateCommandOption::new(
200+
CommandOptionType::SubCommand,
201+
"use",
202+
use_desc,
203+
)
184204
.add_sub_option(
185205
CreateCommandOption::new(CommandOptionType::String, "key", key_desc)
186206
.required(true),
@@ -223,6 +243,9 @@ impl RegistrableCommand for SnippetCommand {
223243
"delete" => {
224244
handle_delete(&ctx, &command, &command.data.options, pool, &config).await
225245
}
246+
"use" => {
247+
handle_use(&ctx, &command, &command.data.options, pool, &config).await
248+
}
226249
_ => {
227250
let response = MessageBuilder::system_message(&ctx, &config)
228251
.translated_content(
@@ -558,3 +581,81 @@ async fn handle_delete(
558581

559582
Ok(())
560583
}
584+
585+
async fn handle_use(
586+
ctx: &Context,
587+
command: &CommandInteraction,
588+
options: &Vec<CommandDataOption>,
589+
pool: &sqlx::SqlitePool,
590+
config: &Config,
591+
) -> ModmailResult<()> {
592+
let mut key = String::new();
593+
594+
if let Some(subcommand) = options.first() {
595+
if let CommandDataOptionValue::SubCommand(sub_options) = &subcommand.value {
596+
for option in sub_options {
597+
if option.name == "key" {
598+
if let CommandDataOptionValue::String(val) = &option.value {
599+
key = val.to_string();
600+
}
601+
}
602+
}
603+
}
604+
}
605+
606+
match get_snippet_by_key(&key, pool).await? {
607+
Some(snippet) => {
608+
let thread = get_thread_by_channel_id(&command.channel_id.to_string(), pool).await;
609+
610+
if let Some(thread) = thread {
611+
let user_id = serenity::all::UserId::new(thread.user_id as u64);
612+
613+
use crate::utils::message::message_builder::StaffReply;
614+
615+
let message_number = allocate_next_message_number(&thread.id, pool).await?;
616+
617+
StaffReply::new(
618+
ctx,
619+
config,
620+
thread.id.clone(),
621+
command.user.id,
622+
command.user.name.clone(),
623+
message_number,
624+
)
625+
.to_thread(command.channel_id)
626+
.to_user(user_id)
627+
.content(snippet.content)
628+
.send_command_and_record(command, pool)
629+
.await?;
630+
631+
let response = MessageBuilder::system_message(ctx, config)
632+
.translated_content(
633+
"snippet.used",
634+
Some(&HashMap::from([("key".to_string(), key.clone())])),
635+
Some(command.user.id),
636+
command.guild_id.map(|g| g.get()),
637+
)
638+
.await
639+
.ephemeral(true)
640+
.build_interaction_message_followup()
641+
.await;
642+
643+
command.create_followup(&ctx.http, response).await?;
644+
} else {
645+
let response = MessageBuilder::system_message(ctx, config)
646+
.content(&snippet.content)
647+
.build_interaction_message_followup()
648+
.await;
649+
650+
command.create_followup(&ctx.http, response).await?;
651+
}
652+
}
653+
None => {
654+
return Err(ModmailError::Command(CommandError::SnippetNotFound(
655+
key.to_string(),
656+
)));
657+
}
658+
}
659+
660+
Ok(())
661+
}

rustmail/src/commands/snippet/text_command/snippet.rs

Lines changed: 88 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub async fn snippet_command(
2020
.as_ref()
2121
.ok_or_else(database_connection_failed)?;
2222

23-
let content = match extract_reply_content(&msg.content, &config.command.prefix, &["snippet"]) {
23+
let content = match extract_reply_content(&msg.content, &config.command.prefix, &["snippet", "s"]) {
2424
Some(c) => c,
2525
None => {
2626
MessageBuilder::system_message(&ctx, config)
@@ -49,18 +49,7 @@ pub async fn snippet_command(
4949
"edit" => handle_edit(&ctx, &msg, args, pool, config).await,
5050
"delete" => handle_delete(&ctx, &msg, args, pool, config).await,
5151
_ => {
52-
MessageBuilder::system_message(&ctx, config)
53-
.translated_content(
54-
"snippet.unknown_text_subcommand",
55-
None,
56-
Some(msg.author.id),
57-
msg.guild_id.map(|g| g.get()),
58-
)
59-
.await
60-
.reply_to(msg)
61-
.send(true)
62-
.await?;
63-
Ok(())
52+
handle_use(&ctx, &msg, subcommand, pool, config).await
6453
}
6554
}
6655
}
@@ -72,9 +61,17 @@ async fn handle_create(
7261
pool: &sqlx::SqlitePool,
7362
config: &Config,
7463
) -> ModmailResult<()> {
75-
let mut parts = args.splitn(2, ' ');
76-
let key = parts.next().unwrap_or("").trim();
77-
let content = parts.next().unwrap_or("").trim();
64+
let trimmed_args = args.trim_start();
65+
let split_pos = trimmed_args
66+
.find(|c: char| c.is_whitespace())
67+
.unwrap_or(trimmed_args.len());
68+
69+
let key = trimmed_args[..split_pos].trim();
70+
let content = if split_pos < trimmed_args.len() {
71+
&trimmed_args[split_pos + 1..]
72+
} else {
73+
""
74+
};
7875

7976
if key.is_empty() || content.is_empty() {
8077
MessageBuilder::system_message(ctx, config)
@@ -267,9 +264,17 @@ async fn handle_edit(
267264
pool: &sqlx::SqlitePool,
268265
config: &Config,
269266
) -> ModmailResult<()> {
270-
let mut parts = args.splitn(2, ' ');
271-
let key = parts.next().unwrap_or("").trim();
272-
let content = parts.next().unwrap_or("").trim();
267+
let trimmed_args = args.trim_start();
268+
let split_pos = trimmed_args
269+
.find(|c: char| c.is_whitespace())
270+
.unwrap_or(trimmed_args.len());
271+
272+
let key = trimmed_args[..split_pos].trim();
273+
let content = if split_pos < trimmed_args.len() {
274+
&trimmed_args[split_pos + 1..]
275+
} else {
276+
""
277+
};
273278

274279
if key.is_empty() || content.is_empty() {
275280
MessageBuilder::system_message(ctx, config)
@@ -367,3 +372,67 @@ async fn handle_delete(
367372

368373
Ok(())
369374
}
375+
376+
async fn handle_use(
377+
ctx: &Context,
378+
msg: &Message,
379+
key: &str,
380+
pool: &sqlx::SqlitePool,
381+
config: &Config,
382+
) -> ModmailResult<()> {
383+
if key.is_empty() {
384+
MessageBuilder::system_message(ctx, config)
385+
.translated_content(
386+
"snippet.text_usage",
387+
None,
388+
Some(msg.author.id),
389+
msg.guild_id.map(|g| g.get()),
390+
)
391+
.await
392+
.reply_to(msg.clone())
393+
.send(true)
394+
.await?;
395+
return Ok(());
396+
}
397+
398+
match get_snippet_by_key(key, pool).await? {
399+
Some(snippet) => {
400+
let thread = get_thread_by_channel_id(&msg.channel_id.to_string(), pool).await;
401+
402+
if let Some(thread) = thread {
403+
let user_id = serenity::all::UserId::new(thread.user_id as u64);
404+
405+
use crate::utils::message::message_builder::StaffReply;
406+
407+
let message_number = allocate_next_message_number(&thread.id, pool).await?;
408+
409+
StaffReply::new(
410+
ctx,
411+
config,
412+
thread.id.clone(),
413+
msg.author.id,
414+
msg.author.name.clone(),
415+
message_number,
416+
)
417+
.to_thread(msg.channel_id)
418+
.to_user(user_id)
419+
.content(snippet.content)
420+
.send_msg_and_record(pool)
421+
.await?;
422+
} else {
423+
MessageBuilder::system_message(ctx, config)
424+
.content(&snippet.content)
425+
.reply_to(msg.clone())
426+
.send(true)
427+
.await?;
428+
}
429+
}
430+
None => {
431+
return Err(ModmailError::Command(CommandError::SnippetNotFound(
432+
key.to_string(),
433+
)));
434+
}
435+
}
436+
437+
Ok(())
438+
}

rustmail/src/handlers/guild_messages_handler.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ impl GuildMessagesHandler {
7878
wrap_command!(lock, "take", take);
7979
wrap_command!(lock, "release", release);
8080
wrap_command!(lock, "ping", ping);
81-
wrap_command!(lock, "snippet", snippet_command);
81+
wrap_command!(lock, ["snippet", "s"], snippet_command);
8282

8383
drop(lock);
8484
h

rustmail/src/i18n/language/en.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -981,10 +981,11 @@ pub fn load_english_messages(dict: &mut ErrorDictionary) {
981981
• `/snippet list` - List all available snippets\n\
982982
• `/snippet show <key>` - Display a specific snippet's content\n\
983983
• `/snippet edit <key> <content>` - Update an existing snippet\n\
984-
• `/snippet delete <key>` - Delete a snippet\n\n\
985-
**Usage:**\n\
986-
• Slash command: `/reply snippet:<key>`\n\
987-
• Text command: `!reply {{key}}`",
984+
• `/snippet delete <key>` - Delete a snippet\n\
985+
• `/snippet use <key>` - Use a snippet to reply\n\n\
986+
**Quick usage:**\n\
987+
• Slash command: `/snippet use <key>` or `/reply snippet:<key>`\n\
988+
• Text command: `!snippet <key>` or `!reply {{key}}`",
988989
),
989990
);
990991
dict.messages.insert(
@@ -1007,6 +1008,10 @@ pub fn load_english_messages(dict: &mut ErrorDictionary) {
10071008
"slash_command.snippet_delete_description".to_string(),
10081009
DictionaryMessage::new("Delete a snippet"),
10091010
);
1011+
dict.messages.insert(
1012+
"slash_command.snippet_use_description".to_string(),
1013+
DictionaryMessage::new("Use a snippet to reply in a ticket"),
1014+
);
10101015
dict.messages.insert(
10111016
"slash_command.snippet_key_argument".to_string(),
10121017
DictionaryMessage::new("Snippet key (alphanumeric, dashes, underscores)"),
@@ -1116,4 +1121,8 @@ pub fn load_english_messages(dict: &mut ErrorDictionary) {
11161121
"Unknown subcommand. Use: `create`, `list`, `show`, `edit`, or `delete`",
11171122
),
11181123
);
1124+
dict.messages.insert(
1125+
"snippet.used".to_string(),
1126+
DictionaryMessage::new("Snippet '**{key}**' used successfully!"),
1127+
);
11191128
}

rustmail/src/i18n/language/fr.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -992,10 +992,11 @@ pub fn load_french_messages(dict: &mut ErrorDictionary) {
992992
• `/snippet list` - Lister tous les snippets disponibles\n\
993993
• `/snippet show <clé>` - Afficher le contenu d'un snippet spécifique\n\
994994
• `/snippet edit <clé> <contenu>` - Modifier un snippet existant\n\
995-
• `/snippet delete <clé>` - Supprimer un snippet\n\n\
996-
**Utilisation :**\n\
997-
• Commande slash : `/reply snippet:<clé>`\n\
998-
• Commande texte : `!reply {{clé}}`",
995+
• `/snippet delete <clé>` - Supprimer un snippet\n\
996+
• `/snippet use <clé>` - Utiliser un snippet pour répondre\n\n\
997+
**Utilisation rapide :**\n\
998+
• Commande slash : `/snippet use <clé>` ou `/reply snippet:<clé>`\n\
999+
• Commande texte : `!snippet <clé>` ou `!reply {{clé}}`",
9991000
),
10001001
);
10011002
dict.messages.insert(
@@ -1018,6 +1019,10 @@ pub fn load_french_messages(dict: &mut ErrorDictionary) {
10181019
"slash_command.snippet_delete_description".to_string(),
10191020
DictionaryMessage::new("Supprimer un snippet"),
10201021
);
1022+
dict.messages.insert(
1023+
"slash_command.snippet_use_description".to_string(),
1024+
DictionaryMessage::new("Utiliser un snippet pour répondre dans un ticket"),
1025+
);
10211026
dict.messages.insert(
10221027
"slash_command.snippet_key_argument".to_string(),
10231028
DictionaryMessage::new("Clé du snippet (alphanumériques, tirets, underscores)"),
@@ -1129,4 +1134,8 @@ pub fn load_french_messages(dict: &mut ErrorDictionary) {
11291134
"Sous-commande inconnue. Utilisez : `create`, `list`, `show`, `edit`, ou `delete`",
11301135
),
11311136
);
1137+
dict.messages.insert(
1138+
"snippet.used".to_string(),
1139+
DictionaryMessage::new("Snippet '**{key}**' utilisé avec succès !"),
1140+
);
11321141
}

0 commit comments

Comments
 (0)