mirror of
https://github.com/teloxide/teloxide.git
synced 2025-03-13 19:27:52 +01:00
Merge pull request #169 from teloxide/fix_parse_command
Fixed command parsing
This commit is contained in:
commit
196528af2b
8 changed files with 120 additions and 49 deletions
|
@ -46,7 +46,7 @@ pin-project = "0.4.6"
|
|||
serde_with_macros = "1.0.1"
|
||||
either = "1.5.3"
|
||||
|
||||
teloxide-macros = "0.1.0"
|
||||
teloxide-macros = { path = "teloxide-macros" }
|
||||
|
||||
[dev-dependencies]
|
||||
smart-default = "0.6.0"
|
||||
|
|
|
@ -136,7 +136,7 @@ async fn answer(
|
|||
|
||||
async fn handle_commands(rx: DispatcherHandlerRx<Message>) {
|
||||
// Only iterate through commands in a proper format:
|
||||
rx.commands::<Command>()
|
||||
rx.commands::<Command, &str>(panic!("Insert here your bot's name"))
|
||||
// Execute all incoming commands concurrently:
|
||||
.for_each_concurrent(None, |(cx, command, _)| async move {
|
||||
answer(cx, command).await.log_on_error().await;
|
||||
|
|
|
@ -179,7 +179,7 @@ async fn handle_commands(rx: DispatcherHandlerRx<Message>) {
|
|||
// Only iterate through messages from groups:
|
||||
rx.filter(|cx| future::ready(cx.update.chat.is_group()))
|
||||
// Only iterate through commands in a proper format:
|
||||
.commands::<Command>()
|
||||
.commands::<Command, &str>(panic!("Insert here your bot's name"))
|
||||
// Execute all incoming commands concurrently:
|
||||
.for_each_concurrent(None, |(cx, command, args)| async move {
|
||||
action(cx, command, &args).await.log_on_error().await;
|
||||
|
|
|
@ -32,7 +32,7 @@ async fn answer(
|
|||
|
||||
async fn handle_commands(rx: DispatcherHandlerRx<Message>) {
|
||||
// Only iterate through commands in a proper format:
|
||||
rx.commands::<Command>()
|
||||
rx.commands::<Command, &str>(panic!("Insert here your bot's name"))
|
||||
// Execute all incoming commands concurrently:
|
||||
.for_each_concurrent(None, |(cx, command, _)| async move {
|
||||
answer(cx, command).await.log_on_error().await;
|
||||
|
|
|
@ -16,12 +16,14 @@ pub trait DispatcherHandlerRxExt {
|
|||
|
||||
/// Extracts only commands with their arguments from this stream of
|
||||
/// arbitrary messages.
|
||||
fn commands<C>(
|
||||
fn commands<C, N>(
|
||||
self,
|
||||
bot_name: N,
|
||||
) -> BoxStream<'static, (DispatcherHandlerCx<Message>, C, Vec<String>)>
|
||||
where
|
||||
Self: Stream<Item = DispatcherHandlerCx<Message>>,
|
||||
C: BotCommand;
|
||||
C: BotCommand,
|
||||
N: Into<String> + Send;
|
||||
}
|
||||
|
||||
impl<T> DispatcherHandlerRxExt for T
|
||||
|
@ -39,23 +41,31 @@ where
|
|||
}))
|
||||
}
|
||||
|
||||
fn commands<C>(
|
||||
fn commands<C, N>(
|
||||
self,
|
||||
bot_name: N,
|
||||
) -> BoxStream<'static, (DispatcherHandlerCx<Message>, C, Vec<String>)>
|
||||
where
|
||||
Self: Stream<Item = DispatcherHandlerCx<Message>>,
|
||||
C: BotCommand,
|
||||
N: Into<String> + Send,
|
||||
{
|
||||
Box::pin(self.text_messages().filter_map(|(cx, text)| async move {
|
||||
C::parse(&text).map(|(command, args)| {
|
||||
(
|
||||
cx,
|
||||
command,
|
||||
args.into_iter()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
})
|
||||
let bot_name = bot_name.into();
|
||||
|
||||
Box::pin(self.text_messages().filter_map(move |(cx, text)| {
|
||||
let bot_name = bot_name.clone();
|
||||
|
||||
async move {
|
||||
C::parse(&text, &bot_name).map(|(command, args)| {
|
||||
(
|
||||
cx,
|
||||
command,
|
||||
args.into_iter()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
//! ([Full](https://github.com/teloxide/teloxide/blob/master/examples/simple_commands_bot/src/main.rs))
|
||||
//! ```no_run
|
||||
//! // Imports are omitted...
|
||||
//! # #[allow(unreachable_code)]
|
||||
//! # use teloxide::{prelude::*, utils::command::BotCommand};
|
||||
//! # use rand::{thread_rng, Rng};
|
||||
//!
|
||||
|
@ -108,7 +109,7 @@
|
|||
//!
|
||||
//! async fn handle_commands(rx: DispatcherHandlerRx<Message>) {
|
||||
//! // Only iterate through commands in a proper format:
|
||||
//! rx.commands::<Command>()
|
||||
//! rx.commands::<Command, &str>(panic!("Insert here your bot's name"))
|
||||
//! // Execute all incoming commands concurrently:
|
||||
//! .for_each_concurrent(None, |(cx, command, _)| async move {
|
||||
//! answer(cx, command).await.log_on_error().await;
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
//! Ban,
|
||||
//! }
|
||||
//!
|
||||
//! let (command, args) = AdminCommand::parse("/ban 3 hours").unwrap();
|
||||
//! let (command, args) =
|
||||
//! AdminCommand::parse("/ban 3 hours", "bot_name").unwrap();
|
||||
//! assert_eq!(command, AdminCommand::Ban);
|
||||
//! assert_eq!(args, vec!["3", "hours"]);
|
||||
//! ```
|
||||
|
@ -24,7 +25,7 @@
|
|||
//! ```
|
||||
//! use teloxide::utils::command::parse_command;
|
||||
//!
|
||||
//! let (command, args) = parse_command("/ban 3 hours").unwrap();
|
||||
//! let (command, args) = parse_command("/ban 3 hours", "").unwrap();
|
||||
//! assert_eq!(command, "/ban");
|
||||
//! assert_eq!(args, vec!["3", "hours"]);
|
||||
//! ```
|
||||
|
@ -34,11 +35,19 @@
|
|||
//! use teloxide::utils::command::parse_command_with_prefix;
|
||||
//!
|
||||
//! let text = "!ban 3 hours";
|
||||
//! let (command, args) = parse_command_with_prefix("!", text).unwrap();
|
||||
//! let (command, args) = parse_command_with_prefix("!", text, "").unwrap();
|
||||
//! assert_eq!(command, "ban");
|
||||
//! assert_eq!(args, vec!["3", "hours"]);
|
||||
//! ```
|
||||
//!
|
||||
//! If the name of a bot does not match, it will return `None`:
|
||||
//! ```
|
||||
//! use teloxide::utils::command::parse_command;
|
||||
//!
|
||||
//! let result = parse_command("/ban@MyNameBot1 3 hours", "MyNameBot2");
|
||||
//! assert!(result.is_none());
|
||||
//! ```
|
||||
//!
|
||||
//! See [examples/admin_bot] as a more complicated examples.
|
||||
//!
|
||||
//! [`parse_command`]: crate::utils::command::parse_command
|
||||
|
@ -61,7 +70,7 @@ pub use teloxide_macros::BotCommand;
|
|||
/// Ban,
|
||||
/// }
|
||||
///
|
||||
/// let (command, args) = AdminCommand::parse("/ban 5 h").unwrap();
|
||||
/// let (command, args) = AdminCommand::parse("/ban 5 h", "bot_name").unwrap();
|
||||
/// assert_eq!(command, AdminCommand::Ban);
|
||||
/// assert_eq!(args, vec!["5", "h"]);
|
||||
/// ```
|
||||
|
@ -92,7 +101,9 @@ pub use teloxide_macros::BotCommand;
|
|||
pub trait BotCommand: Sized {
|
||||
fn try_from(s: &str) -> Option<Self>;
|
||||
fn descriptions() -> String;
|
||||
fn parse(s: &str) -> Option<(Self, Vec<&str>)>;
|
||||
fn parse<N>(s: &str, bot_name: N) -> Option<(Self, Vec<&str>)>
|
||||
where
|
||||
N: Into<String>;
|
||||
}
|
||||
|
||||
/// Parses a string into a command with args.
|
||||
|
@ -103,14 +114,24 @@ pub trait BotCommand: Sized {
|
|||
/// ```
|
||||
/// use teloxide::utils::command::parse_command;
|
||||
///
|
||||
/// let text = "/mute 5 hours";
|
||||
/// let (command, args) = parse_command(text).unwrap();
|
||||
/// let text = "/mute@my_admin_bot 5 hours";
|
||||
/// let (command, args) = parse_command(text, "my_admin_bot").unwrap();
|
||||
/// assert_eq!(command, "/mute");
|
||||
/// assert_eq!(args, vec!["5", "hours"]);
|
||||
/// ```
|
||||
pub fn parse_command(text: &str) -> Option<(&str, Vec<&str>)> {
|
||||
pub fn parse_command<N>(text: &str, bot_name: N) -> Option<(&str, Vec<&str>)>
|
||||
where
|
||||
N: AsRef<str>,
|
||||
{
|
||||
let mut words = text.split_whitespace();
|
||||
let command = words.next()?;
|
||||
let mut splited = words.next()?.split('@');
|
||||
let command = splited.next()?;
|
||||
let bot = splited.next();
|
||||
match bot {
|
||||
Some(name) if name == bot_name.as_ref() => {}
|
||||
None => {}
|
||||
_ => return None,
|
||||
}
|
||||
Some((command, words.collect()))
|
||||
}
|
||||
|
||||
|
@ -123,19 +144,30 @@ pub fn parse_command(text: &str) -> Option<(&str, Vec<&str>)> {
|
|||
/// use teloxide::utils::command::parse_command_with_prefix;
|
||||
///
|
||||
/// let text = "!mute 5 hours";
|
||||
/// let (command, args) = parse_command_with_prefix("!", text).unwrap();
|
||||
/// let (command, args) = parse_command_with_prefix("!", text, "").unwrap();
|
||||
/// assert_eq!(command, "mute");
|
||||
/// assert_eq!(args, vec!["5", "hours"]);
|
||||
/// ```
|
||||
pub fn parse_command_with_prefix<'a>(
|
||||
pub fn parse_command_with_prefix<'a, N>(
|
||||
prefix: &str,
|
||||
text: &'a str,
|
||||
) -> Option<(&'a str, Vec<&'a str>)> {
|
||||
bot_name: N,
|
||||
) -> Option<(&'a str, Vec<&'a str>)>
|
||||
where
|
||||
N: AsRef<str>,
|
||||
{
|
||||
if !text.starts_with(prefix) {
|
||||
return None;
|
||||
}
|
||||
let mut words = text.split_whitespace();
|
||||
let command = &words.next()?[prefix.len()..];
|
||||
let mut splited = words.next()?[prefix.len()..].split('@');
|
||||
let command = splited.next()?;
|
||||
let bot = splited.next();
|
||||
match bot {
|
||||
Some(name) if name == bot_name.as_ref() => {}
|
||||
None => {}
|
||||
_ => return None,
|
||||
}
|
||||
Some((command, words.collect()))
|
||||
}
|
||||
|
||||
|
@ -147,7 +179,7 @@ mod tests {
|
|||
fn parse_command_with_args_() {
|
||||
let data = "/command arg1 arg2";
|
||||
let expected = Some(("/command", vec!["arg1", "arg2"]));
|
||||
let actual = parse_command(data);
|
||||
let actual = parse_command(data, "");
|
||||
assert_eq!(actual, expected)
|
||||
}
|
||||
|
||||
|
@ -155,7 +187,7 @@ mod tests {
|
|||
fn parse_command_with_args_without_args() {
|
||||
let data = "/command";
|
||||
let expected = Some(("/command", vec![]));
|
||||
let actual = parse_command(data);
|
||||
let actual = parse_command(data, "");
|
||||
assert_eq!(actual, expected)
|
||||
}
|
||||
|
||||
|
@ -170,7 +202,7 @@ mod tests {
|
|||
|
||||
let data = "/start arg1 arg2";
|
||||
let expected = Some((DefaultCommands::Start, vec!["arg1", "arg2"]));
|
||||
let actual = DefaultCommands::parse(data);
|
||||
let actual = DefaultCommands::parse(data, "");
|
||||
assert_eq!(actual, expected)
|
||||
}
|
||||
|
||||
|
@ -186,7 +218,7 @@ mod tests {
|
|||
|
||||
let data = "!start arg1 arg2";
|
||||
let expected = Some((DefaultCommands::Start, vec!["arg1", "arg2"]));
|
||||
let actual = DefaultCommands::parse(data);
|
||||
let actual = DefaultCommands::parse(data, "");
|
||||
assert_eq!(actual, expected)
|
||||
}
|
||||
|
||||
|
@ -202,12 +234,9 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
DefaultCommands::Start,
|
||||
DefaultCommands::parse("!start").unwrap().0
|
||||
);
|
||||
assert_eq!(
|
||||
DefaultCommands::descriptions(),
|
||||
"!start - desc\n/help - \n"
|
||||
DefaultCommands::parse("!start", "").unwrap().0
|
||||
);
|
||||
assert_eq!(DefaultCommands::descriptions(), "!start - desc\n/help\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -226,15 +255,31 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
DefaultCommands::Start,
|
||||
DefaultCommands::parse("/start").unwrap().0
|
||||
DefaultCommands::parse("/start", "MyNameBot").unwrap().0
|
||||
);
|
||||
assert_eq!(
|
||||
DefaultCommands::Help,
|
||||
DefaultCommands::parse("!help").unwrap().0
|
||||
DefaultCommands::parse("!help", "MyNameBot").unwrap().0
|
||||
);
|
||||
assert_eq!(
|
||||
DefaultCommands::descriptions(),
|
||||
"Bot commands\n/start - \n!help - \n"
|
||||
"Bot commands\n/start\n!help\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_command_with_bot_name() {
|
||||
#[command(rename = "lowercase")]
|
||||
#[derive(BotCommand, Debug, PartialEq)]
|
||||
enum DefaultCommands {
|
||||
#[command(prefix = "/")]
|
||||
Start,
|
||||
Help,
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
DefaultCommands::Start,
|
||||
DefaultCommands::parse("/start@MyNameBot", "MyNameBot").unwrap().0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,9 +82,12 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream {
|
|||
.zip(variant_name)
|
||||
.map(|(prefix, command)| prefix.to_string() + command.as_str());
|
||||
let variant_str2 = variant_str1.clone();
|
||||
let variant_description = variant_infos
|
||||
.iter()
|
||||
.map(|info| info.description.as_deref().unwrap_or(""));
|
||||
let variant_description = variant_infos.iter().map(|info| {
|
||||
info.description
|
||||
.as_deref()
|
||||
.map(|e| format!(" - {}", e))
|
||||
.unwrap_or(String::new())
|
||||
});
|
||||
|
||||
let ident = &input.ident;
|
||||
|
||||
|
@ -105,11 +108,23 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream {
|
|||
}
|
||||
}
|
||||
fn descriptions() -> String {
|
||||
std::concat!(#global_description #(#variant_str2, " - ", #variant_description, '\n'),*).to_string()
|
||||
std::concat!(#global_description #(#variant_str2, #variant_description, '\n'),*).to_string()
|
||||
}
|
||||
fn parse(s: &str) -> Option<(Self, Vec<&str>)> {
|
||||
fn parse<N>(s: &str, bot_name: N) -> Option<(Self, Vec<&str>)>
|
||||
where
|
||||
N: Into<String>
|
||||
{
|
||||
let mut words = s.split_whitespace();
|
||||
let command = Self::try_from(words.next()?)?;
|
||||
let mut splited = words.next()?.split('@');
|
||||
let command_raw = splited.next()?;
|
||||
let bot = splited.next();
|
||||
let bot_name = bot_name.into();
|
||||
match bot {
|
||||
Some(name) if name == bot_name => {}
|
||||
None => {}
|
||||
_ => return None,
|
||||
}
|
||||
let command = Self::try_from(command_raw)?;
|
||||
Some((command, words.collect()))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue