mirror of
https://github.com/teloxide/teloxide.git
synced 2024-10-23 17:36:54 +02:00
Merge pull request #550 from teloxide/bot_command_better_descriptions
Refactor `BotCommand` trait & related stuff
This commit is contained in:
commit
30059b3455
18 changed files with 294 additions and 127 deletions
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
@ -6,6 +6,9 @@ on:
|
||||||
|
|
||||||
name: Continuous integration
|
name: Continuous integration
|
||||||
|
|
||||||
|
env:
|
||||||
|
RUSTFLAGS: "--cfg CI_REDIS"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
style:
|
style:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
|
@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- `UpdateListener::StopToken` is now always `Send`
|
- `UpdateListener::StopToken` is now always `Send`
|
||||||
|
- Rename `BotCommand` trait to `BotCommands`
|
||||||
|
- `BotCommands::descriptions` now returns `CommandDescriptions` instead of `String`
|
||||||
|
|
||||||
## 0.7.2 - 2022-03-23
|
## 0.7.2 - 2022-03-23
|
||||||
|
|
||||||
|
@ -26,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
- Log `UpdateKind::Error` in `teloxide::dispatching2::Dispatcher`.
|
- Log `UpdateKind::Error` in `teloxide::dispatching2::Dispatcher`.
|
||||||
- Don't warn about unhandled updates in `repls2` ([issue 557](https://github.com/teloxide/teloxide/issues/557)).
|
- Don't warn about unhandled updates in `repls2` ([issue 557](https://github.com/teloxide/teloxide/issues/557)).
|
||||||
|
- `parse_command` and `parse_command_with_prefix` now ignores case of the bot username
|
||||||
|
|
||||||
## 0.7.1 - 2022-03-09
|
## 0.7.1 - 2022-03-09
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,8 @@ full = [
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
teloxide-core = { version = "0.4", default-features = false }
|
teloxide-core = { version = "0.4", default-features = false }
|
||||||
teloxide-macros = { version = "0.5.1", optional = true }
|
#teloxide-macros = { version = "0.5.1", optional = true }
|
||||||
|
teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", rev = "7e000b9", optional = true }
|
||||||
|
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
|
@ -125,11 +125,11 @@ Commands are strongly typed and defined declaratively, similar to how we define
|
||||||
([Full](examples/simple_commands.rs))
|
([Full](examples/simple_commands.rs))
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
use teloxide::{prelude2::*, utils::command::BotCommand};
|
use teloxide::{prelude2::*, utils::command::BotCommands};
|
||||||
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
#[derive(BotCommand, Clone)]
|
#[derive(BotCommands, Clone)]
|
||||||
#[command(rename = "lowercase", description = "These commands are supported:")]
|
#[command(rename = "lowercase", description = "These commands are supported:")]
|
||||||
enum Command {
|
enum Command {
|
||||||
#[command(description = "display this text.")]
|
#[command(description = "display this text.")]
|
||||||
|
@ -146,7 +146,7 @@ async fn answer(
|
||||||
command: Command,
|
command: Command,
|
||||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
match command {
|
match command {
|
||||||
Command::Help => bot.send_message(message.chat.id, Command::descriptions()).await?,
|
Command::Help => bot.send_message(message.chat.id, Command::descriptions().to_string()).await?,
|
||||||
Command::Username(username) => {
|
Command::Username(username) => {
|
||||||
bot.send_message(message.chat.id, format!("Your username is @{}.", username)).await?
|
bot.send_message(message.chat.id, format!("Your username is @{}.", username)).await?
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use std::{error::Error, str::FromStr};
|
use std::{error::Error, str::FromStr};
|
||||||
|
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use teloxide::{prelude2::*, types::ChatPermissions, utils::command::BotCommand};
|
use teloxide::{prelude2::*, types::ChatPermissions, utils::command::BotCommands};
|
||||||
|
|
||||||
// Derive BotCommand to parse text with a command into this enumeration.
|
// Derive BotCommands to parse text with a command into this enumeration.
|
||||||
//
|
//
|
||||||
// 1. rename = "lowercase" turns all the commands into lowercase letters.
|
// 1. rename = "lowercase" turns all the commands into lowercase letters.
|
||||||
// 2. `description = "..."` specifies a text before all the commands.
|
// 2. `description = "..."` specifies a text before all the commands.
|
||||||
|
@ -12,7 +12,7 @@ use teloxide::{prelude2::*, types::ChatPermissions, utils::command::BotCommand};
|
||||||
// your commands in this format:
|
// your commands in this format:
|
||||||
// %GENERAL-DESCRIPTION%
|
// %GENERAL-DESCRIPTION%
|
||||||
// %PREFIX%%COMMAND% - %DESCRIPTION%
|
// %PREFIX%%COMMAND% - %DESCRIPTION%
|
||||||
#[derive(BotCommand, Clone)]
|
#[derive(BotCommands, Clone)]
|
||||||
#[command(
|
#[command(
|
||||||
rename = "lowercase",
|
rename = "lowercase",
|
||||||
description = "Use commands in format /%command% %num% %unit%",
|
description = "Use commands in format /%command% %num% %unit%",
|
||||||
|
@ -132,7 +132,7 @@ async fn action(
|
||||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
match command {
|
match command {
|
||||||
Command::Help => {
|
Command::Help => {
|
||||||
bot.send_message(msg.chat.id, Command::descriptions()).await?;
|
bot.send_message(msg.chat.id, Command::descriptions().to_string()).await?;
|
||||||
}
|
}
|
||||||
Command::Kick => kick_user(bot, msg).await?,
|
Command::Kick => kick_user(bot, msg).await?,
|
||||||
Command::Ban { time, unit } => ban_user(bot, msg, calc_restrict_time(time, unit)).await?,
|
Command::Ban { time, unit } => ban_user(bot, msg, calc_restrict_time(time, unit)).await?,
|
||||||
|
|
|
@ -6,10 +6,10 @@ use teloxide::{
|
||||||
InlineKeyboardButton, InlineKeyboardMarkup, InlineQueryResultArticle, InputMessageContent,
|
InlineKeyboardButton, InlineKeyboardMarkup, InlineQueryResultArticle, InputMessageContent,
|
||||||
InputMessageContentText,
|
InputMessageContentText,
|
||||||
},
|
},
|
||||||
utils::command::BotCommand,
|
utils::command::BotCommands,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(BotCommand)]
|
#[derive(BotCommands)]
|
||||||
#[command(rename = "lowercase", description = "These commands are supported:")]
|
#[command(rename = "lowercase", description = "These commands are supported:")]
|
||||||
enum Command {
|
enum Command {
|
||||||
#[command(description = "Display this text")]
|
#[command(description = "Display this text")]
|
||||||
|
@ -47,10 +47,10 @@ async fn message_handler(
|
||||||
bot: AutoSend<Bot>,
|
bot: AutoSend<Bot>,
|
||||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
if let Some(text) = m.text() {
|
if let Some(text) = m.text() {
|
||||||
match BotCommand::parse(text, "buttons") {
|
match BotCommands::parse(text, "buttons") {
|
||||||
Ok(Command::Help) => {
|
Ok(Command::Help) => {
|
||||||
// Just send the description of all commands.
|
// Just send the description of all commands.
|
||||||
bot.send_message(m.chat.id, Command::descriptions()).await?;
|
bot.send_message(m.chat.id, Command::descriptions().to_string()).await?;
|
||||||
}
|
}
|
||||||
Ok(Command::Start) => {
|
Ok(Command::Start) => {
|
||||||
// Create a list of buttons and send them.
|
// Create a list of buttons and send them.
|
||||||
|
|
|
@ -9,7 +9,7 @@ use teloxide::{
|
||||||
macros::DialogueState,
|
macros::DialogueState,
|
||||||
prelude2::*,
|
prelude2::*,
|
||||||
types::Me,
|
types::Me,
|
||||||
utils::command::BotCommand,
|
utils::command::BotCommands,
|
||||||
};
|
};
|
||||||
|
|
||||||
type MyDialogue = Dialogue<State, ErasedStorage<State>>;
|
type MyDialogue = Dialogue<State, ErasedStorage<State>>;
|
||||||
|
@ -32,7 +32,7 @@ impl Default for State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(BotCommand)]
|
#[derive(BotCommands)]
|
||||||
#[command(rename = "lowercase", description = "These commands are supported:")]
|
#[command(rename = "lowercase", description = "These commands are supported:")]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
#[command(description = "get your number.")]
|
#[command(description = "get your number.")]
|
||||||
|
|
|
@ -7,8 +7,8 @@ use rand::Rng;
|
||||||
// dispatching system, which will be deprecated in the future.
|
// dispatching system, which will be deprecated in the future.
|
||||||
use teloxide::{
|
use teloxide::{
|
||||||
prelude2::*,
|
prelude2::*,
|
||||||
types::{Dice, Update},
|
types::{Dice, Me, Update},
|
||||||
utils::command::BotCommand,
|
utils::command::BotCommands,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -26,28 +26,6 @@ async fn main() {
|
||||||
let handler = Update::filter_message()
|
let handler = Update::filter_message()
|
||||||
// You can use branching to define multiple ways in which an update will be handled. If the
|
// You can use branching to define multiple ways in which an update will be handled. If the
|
||||||
// first branch fails, an update will be passed to the second branch, and so on.
|
// first branch fails, an update will be passed to the second branch, and so on.
|
||||||
.branch(
|
|
||||||
// Filtering allow you to filter updates by some condition.
|
|
||||||
dptree::filter(|msg: Message| msg.chat.is_group() || msg.chat.is_supergroup())
|
|
||||||
// An endpoint is the last update handler.
|
|
||||||
.endpoint(|msg: Message, bot: AutoSend<Bot>| async move {
|
|
||||||
log::info!("Received a message from a group chat.");
|
|
||||||
bot.send_message(msg.chat.id, "This is a group chat.").await?;
|
|
||||||
respond(())
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.branch(
|
|
||||||
// There are some extension filtering functions on `Message`. The following filter will
|
|
||||||
// filter only messages with dices.
|
|
||||||
Message::filter_dice().endpoint(
|
|
||||||
|msg: Message, dice: Dice, bot: AutoSend<Bot>| async move {
|
|
||||||
bot.send_message(msg.chat.id, format!("Dice value: {}", dice.value))
|
|
||||||
.reply_to_message_id(msg.id)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.branch(
|
.branch(
|
||||||
dptree::entry()
|
dptree::entry()
|
||||||
// Filter commands: the next handlers will receive a parsed `SimpleCommand`.
|
// Filter commands: the next handlers will receive a parsed `SimpleCommand`.
|
||||||
|
@ -74,6 +52,28 @@ async fn main() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
.branch(
|
||||||
|
// Filtering allow you to filter updates by some condition.
|
||||||
|
dptree::filter(|msg: Message| msg.chat.is_group() || msg.chat.is_supergroup())
|
||||||
|
// An endpoint is the last update handler.
|
||||||
|
.endpoint(|msg: Message, bot: AutoSend<Bot>| async move {
|
||||||
|
log::info!("Received a message from a group chat.");
|
||||||
|
bot.send_message(msg.chat.id, "This is a group chat.").await?;
|
||||||
|
respond(())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.branch(
|
||||||
|
// There are some extension filtering functions on `Message`. The following filter will
|
||||||
|
// filter only messages with dices.
|
||||||
|
Message::filter_dice().endpoint(
|
||||||
|
|msg: Message, dice: Dice, bot: AutoSend<Bot>| async move {
|
||||||
|
bot.send_message(msg.chat.id, format!("Dice value: {}", dice.value))
|
||||||
|
.reply_to_message_id(msg.id)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Dispatcher::builder(bot, handler)
|
Dispatcher::builder(bot, handler)
|
||||||
|
@ -101,7 +101,7 @@ struct ConfigParameters {
|
||||||
maintainer_username: Option<String>,
|
maintainer_username: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(BotCommand, Clone)]
|
#[derive(BotCommands, Clone)]
|
||||||
#[command(rename = "lowercase", description = "Simple commands")]
|
#[command(rename = "lowercase", description = "Simple commands")]
|
||||||
enum SimpleCommand {
|
enum SimpleCommand {
|
||||||
#[command(description = "shows this message.")]
|
#[command(description = "shows this message.")]
|
||||||
|
@ -112,7 +112,7 @@ enum SimpleCommand {
|
||||||
MyId,
|
MyId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(BotCommand, Clone)]
|
#[derive(BotCommands, Clone)]
|
||||||
#[command(rename = "lowercase", description = "Maintainer commands")]
|
#[command(rename = "lowercase", description = "Maintainer commands")]
|
||||||
enum MaintainerCommands {
|
enum MaintainerCommands {
|
||||||
#[command(parse_with = "split", description = "generate a number within range")]
|
#[command(parse_with = "split", description = "generate a number within range")]
|
||||||
|
@ -124,13 +124,20 @@ async fn simple_commands_handler(
|
||||||
bot: AutoSend<Bot>,
|
bot: AutoSend<Bot>,
|
||||||
cmd: SimpleCommand,
|
cmd: SimpleCommand,
|
||||||
cfg: ConfigParameters,
|
cfg: ConfigParameters,
|
||||||
|
me: Me,
|
||||||
) -> Result<(), teloxide::RequestError> {
|
) -> Result<(), teloxide::RequestError> {
|
||||||
let text = match cmd {
|
let text = match cmd {
|
||||||
SimpleCommand::Help => {
|
SimpleCommand::Help => {
|
||||||
if msg.from().unwrap().id == cfg.bot_maintainer {
|
if msg.from().unwrap().id == cfg.bot_maintainer {
|
||||||
format!("{}\n{}", SimpleCommand::descriptions(), MaintainerCommands::descriptions())
|
format!(
|
||||||
|
"{}\n\n{}",
|
||||||
|
SimpleCommand::descriptions(),
|
||||||
|
MaintainerCommands::descriptions()
|
||||||
|
)
|
||||||
|
} else if msg.chat.is_group() || msg.chat.is_supergroup() {
|
||||||
|
SimpleCommand::descriptions().username_from_me(&me).to_string()
|
||||||
} else {
|
} else {
|
||||||
SimpleCommand::descriptions()
|
SimpleCommand::descriptions().to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SimpleCommand::Maintainer => {
|
SimpleCommand::Maintainer => {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use teloxide::{prelude2::*, utils::command::BotCommand};
|
use teloxide::{prelude2::*, utils::command::BotCommands};
|
||||||
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
#[derive(BotCommand, Clone)]
|
#[derive(BotCommands, Clone)]
|
||||||
#[command(rename = "lowercase", description = "These commands are supported:")]
|
#[command(rename = "lowercase", description = "These commands are supported:")]
|
||||||
enum Command {
|
enum Command {
|
||||||
#[command(description = "display this text.")]
|
#[command(description = "display this text.")]
|
||||||
|
@ -19,7 +19,9 @@ async fn answer(
|
||||||
command: Command,
|
command: Command,
|
||||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
match command {
|
match command {
|
||||||
Command::Help => bot.send_message(message.chat.id, Command::descriptions()).await?,
|
Command::Help => {
|
||||||
|
bot.send_message(message.chat.id, Command::descriptions().to_string()).await?
|
||||||
|
}
|
||||||
Command::Username(username) => {
|
Command::Username(username) => {
|
||||||
bot.send_message(message.chat.id, format!("Your username is @{}.", username)).await?
|
bot.send_message(message.chat.id, format!("Your username is @{}.", username)).await?
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{dispatching::UpdateWithCx, utils::command::BotCommand};
|
use crate::{dispatching::UpdateWithCx, utils::command::BotCommands};
|
||||||
use futures::{stream::BoxStream, Stream, StreamExt};
|
use futures::{stream::BoxStream, Stream, StreamExt};
|
||||||
use teloxide_core::types::Message;
|
use teloxide_core::types::Message;
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ pub trait DispatcherHandlerRxExt<R> {
|
||||||
fn commands<C, N>(self, bot_name: N) -> BoxStream<'static, (UpdateWithCx<R, Message>, C)>
|
fn commands<C, N>(self, bot_name: N) -> BoxStream<'static, (UpdateWithCx<R, Message>, C)>
|
||||||
where
|
where
|
||||||
Self: Stream<Item = UpdateWithCx<R, Message>>,
|
Self: Stream<Item = UpdateWithCx<R, Message>>,
|
||||||
C: BotCommand,
|
C: BotCommands,
|
||||||
N: Into<String> + Send,
|
N: Into<String> + Send,
|
||||||
R: Send + 'static;
|
R: Send + 'static;
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ where
|
||||||
fn commands<C, N>(self, bot_name: N) -> BoxStream<'static, (UpdateWithCx<R, Message>, C)>
|
fn commands<C, N>(self, bot_name: N) -> BoxStream<'static, (UpdateWithCx<R, Message>, C)>
|
||||||
where
|
where
|
||||||
Self: Stream<Item = UpdateWithCx<R, Message>>,
|
Self: Stream<Item = UpdateWithCx<R, Message>>,
|
||||||
C: BotCommand,
|
C: BotCommands,
|
||||||
N: Into<String> + Send,
|
N: Into<String> + Send,
|
||||||
R: Send + 'static,
|
R: Send + 'static,
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
||||||
DispatcherHandlerRxExt, UpdateWithCx,
|
DispatcherHandlerRxExt, UpdateWithCx,
|
||||||
},
|
},
|
||||||
error_handlers::{LoggingErrorHandler, OnError},
|
error_handlers::{LoggingErrorHandler, OnError},
|
||||||
utils::command::BotCommand,
|
utils::command::BotCommands,
|
||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use std::{fmt::Debug, future::Future, sync::Arc};
|
use std::{fmt::Debug, future::Future, sync::Arc};
|
||||||
|
@ -25,7 +25,7 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||||
#[cfg(feature = "ctrlc_handler")]
|
#[cfg(feature = "ctrlc_handler")]
|
||||||
pub async fn commands_repl<R, Cmd, H, Fut, HandlerE, N>(requester: R, bot_name: N, handler: H)
|
pub async fn commands_repl<R, Cmd, H, Fut, HandlerE, N>(requester: R, bot_name: N, handler: H)
|
||||||
where
|
where
|
||||||
Cmd: BotCommand + Send + 'static,
|
Cmd: BotCommands + Send + 'static,
|
||||||
H: Fn(UpdateWithCx<R, Message>, Cmd) -> Fut + Send + Sync + 'static,
|
H: Fn(UpdateWithCx<R, Message>, Cmd) -> Fut + Send + Sync + 'static,
|
||||||
Fut: Future<Output = Result<(), HandlerE>> + Send + 'static,
|
Fut: Future<Output = Result<(), HandlerE>> + Send + 'static,
|
||||||
Result<(), HandlerE>: OnError<HandlerE>,
|
Result<(), HandlerE>: OnError<HandlerE>,
|
||||||
|
@ -64,7 +64,7 @@ pub async fn commands_repl_with_listener<'a, R, Cmd, H, Fut, L, ListenerE, Handl
|
||||||
handler: H,
|
handler: H,
|
||||||
listener: L,
|
listener: L,
|
||||||
) where
|
) where
|
||||||
Cmd: BotCommand + Send + 'static,
|
Cmd: BotCommands + Send + 'static,
|
||||||
H: Fn(UpdateWithCx<R, Message>, Cmd) -> Fut + Send + Sync + 'static,
|
H: Fn(UpdateWithCx<R, Message>, Cmd) -> Fut + Send + Sync + 'static,
|
||||||
Fut: Future<Output = Result<(), HandlerE>> + Send + 'static,
|
Fut: Future<Output = Result<(), HandlerE>> + Send + 'static,
|
||||||
L: UpdateListener<ListenerE> + Send + 'a,
|
L: UpdateListener<ListenerE> + Send + 'a,
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
HandlerFactory,
|
HandlerFactory,
|
||||||
},
|
},
|
||||||
types::{Me, Message},
|
types::{Me, Message},
|
||||||
utils::command::BotCommand,
|
utils::command::BotCommands,
|
||||||
};
|
};
|
||||||
use dptree::{di::DependencyMap, Handler};
|
use dptree::{di::DependencyMap, Handler};
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ pub trait HandlerExt<Output> {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn filter_command<C>(self) -> Self
|
fn filter_command<C>(self) -> Self
|
||||||
where
|
where
|
||||||
C: BotCommand + Send + Sync + 'static;
|
C: BotCommands + Send + Sync + 'static;
|
||||||
|
|
||||||
/// Passes [`Dialogue<D, S>`] and `D` as handler dependencies.
|
/// Passes [`Dialogue<D, S>`] and `D` as handler dependencies.
|
||||||
///
|
///
|
||||||
|
@ -62,7 +62,7 @@ where
|
||||||
{
|
{
|
||||||
fn filter_command<C>(self) -> Self
|
fn filter_command<C>(self) -> Self
|
||||||
where
|
where
|
||||||
C: BotCommand + Send + Sync + 'static,
|
C: BotCommands + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
self.chain(dptree::filter_map(move |message: Message, me: Me| {
|
self.chain(dptree::filter_map(move |message: Message, me: Me| {
|
||||||
let bot_name = me.user.username.expect("Bots must have a username");
|
let bot_name = me.user.username.expect("Bots must have a username");
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
||||||
dispatching2::{HandlerExt, UpdateFilterExt},
|
dispatching2::{HandlerExt, UpdateFilterExt},
|
||||||
error_handlers::LoggingErrorHandler,
|
error_handlers::LoggingErrorHandler,
|
||||||
types::Update,
|
types::Update,
|
||||||
utils::command::BotCommand,
|
utils::command::BotCommands,
|
||||||
};
|
};
|
||||||
use dptree::di::{DependencyMap, Injectable};
|
use dptree::di::{DependencyMap, Injectable};
|
||||||
use std::{fmt::Debug, marker::PhantomData};
|
use std::{fmt::Debug, marker::PhantomData};
|
||||||
|
@ -27,7 +27,7 @@ use teloxide_core::requests::Requester;
|
||||||
#[cfg(feature = "ctrlc_handler")]
|
#[cfg(feature = "ctrlc_handler")]
|
||||||
pub async fn commands_repl<'a, R, Cmd, H, E, Args>(bot: R, handler: H, cmd: PhantomData<Cmd>)
|
pub async fn commands_repl<'a, R, Cmd, H, E, Args>(bot: R, handler: H, cmd: PhantomData<Cmd>)
|
||||||
where
|
where
|
||||||
Cmd: BotCommand + Send + Sync + 'static,
|
Cmd: BotCommands + Send + Sync + 'static,
|
||||||
H: Injectable<DependencyMap, Result<(), E>, Args> + Send + Sync + 'static,
|
H: Injectable<DependencyMap, Result<(), E>, Args> + Send + Sync + 'static,
|
||||||
R: Requester + Clone + Send + Sync + 'static,
|
R: Requester + Clone + Send + Sync + 'static,
|
||||||
<R as Requester>::GetUpdates: Send,
|
<R as Requester>::GetUpdates: Send,
|
||||||
|
@ -67,7 +67,7 @@ pub async fn commands_repl_with_listener<'a, R, Cmd, H, L, ListenerE, E, Args>(
|
||||||
listener: L,
|
listener: L,
|
||||||
_cmd: PhantomData<Cmd>,
|
_cmd: PhantomData<Cmd>,
|
||||||
) where
|
) where
|
||||||
Cmd: BotCommand + Send + Sync + 'static,
|
Cmd: BotCommands + Send + Sync + 'static,
|
||||||
H: Injectable<DependencyMap, Result<(), E>, Args> + Send + Sync + 'static,
|
H: Injectable<DependencyMap, Result<(), E>, Args> + Send + Sync + 'static,
|
||||||
L: UpdateListener<ListenerE> + Send + 'a,
|
L: UpdateListener<ListenerE> + Send + 'a,
|
||||||
ListenerE: Debug + Send + 'a,
|
ListenerE: Debug + Send + 'a,
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
//! Command parsers.
|
//! Command parsers.
|
||||||
//!
|
//!
|
||||||
//! You can either create an `enum` with derived [`BotCommand`], containing
|
//! You can either create an `enum` with derived [`BotCommands`], containing
|
||||||
//! commands of your bot, or use functions, which split input text into a string
|
//! commands of your bot, or use functions, which split input text into a string
|
||||||
//! command with its arguments.
|
//! command with its arguments.
|
||||||
//!
|
//!
|
||||||
//! # Using BotCommand
|
//! # Using BotCommands
|
||||||
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # #[cfg(feature = "macros")] {
|
//! # #[cfg(feature = "macros")] {
|
||||||
//! use teloxide::utils::command::BotCommand;
|
//! use teloxide::utils::command::BotCommands;
|
||||||
//!
|
//!
|
||||||
//! type UnitOfTime = u8;
|
//! type UnitOfTime = u8;
|
||||||
//!
|
//!
|
||||||
//! #[derive(BotCommand, PartialEq, Debug)]
|
//! #[derive(BotCommands, PartialEq, Debug)]
|
||||||
//! #[command(rename = "lowercase", parse_with = "split")]
|
//! #[command(rename = "lowercase", parse_with = "split")]
|
||||||
//! enum AdminCommand {
|
//! enum AdminCommand {
|
||||||
//! Mute(UnitOfTime, char),
|
//! Mute(UnitOfTime, char),
|
||||||
|
@ -24,6 +25,7 @@
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! # Using parse_command
|
//! # Using parse_command
|
||||||
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use teloxide::utils::command::parse_command;
|
//! use teloxide::utils::command::parse_command;
|
||||||
//!
|
//!
|
||||||
|
@ -33,6 +35,7 @@
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! # Using parse_command_with_prefix
|
//! # Using parse_command_with_prefix
|
||||||
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use teloxide::utils::command::parse_command_with_prefix;
|
//! use teloxide::utils::command::parse_command_with_prefix;
|
||||||
//!
|
//!
|
||||||
|
@ -46,25 +49,27 @@
|
||||||
//!
|
//!
|
||||||
//! [examples/admin_bot]: https://github.com/teloxide/teloxide/blob/master/examples/admin_bot/
|
//! [examples/admin_bot]: https://github.com/teloxide/teloxide/blob/master/examples/admin_bot/
|
||||||
|
|
||||||
|
use core::fmt;
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
fmt::{Display, Formatter},
|
fmt::{Display, Formatter, Write},
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use teloxide_core::types::{BotCommand, Me};
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
pub use teloxide_macros::BotCommand;
|
pub use teloxide_macros::BotCommands;
|
||||||
|
|
||||||
/// An enumeration of bot's commands.
|
/// An enumeration of bot's commands.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # #[cfg(feature = "macros")] {
|
/// # #[cfg(feature = "macros")] {
|
||||||
/// use teloxide::utils::command::BotCommand;
|
/// use teloxide::utils::command::BotCommands;
|
||||||
///
|
///
|
||||||
/// type UnitOfTime = u8;
|
/// type UnitOfTime = u8;
|
||||||
///
|
///
|
||||||
/// #[derive(BotCommand, PartialEq, Debug)]
|
/// #[derive(BotCommands, PartialEq, Debug)]
|
||||||
/// #[command(rename = "lowercase", parse_with = "split")]
|
/// #[command(rename = "lowercase", parse_with = "split")]
|
||||||
/// enum AdminCommand {
|
/// enum AdminCommand {
|
||||||
/// Mute(UnitOfTime, char),
|
/// Mute(UnitOfTime, char),
|
||||||
|
@ -98,9 +103,9 @@ pub use teloxide_macros::BotCommand;
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # #[cfg(feature = "macros")] {
|
/// # #[cfg(feature = "macros")] {
|
||||||
/// use teloxide::utils::command::BotCommand;
|
/// use teloxide::utils::command::BotCommands;
|
||||||
///
|
///
|
||||||
/// #[derive(BotCommand, PartialEq, Debug)]
|
/// #[derive(BotCommands, PartialEq, Debug)]
|
||||||
/// #[command(rename = "lowercase")]
|
/// #[command(rename = "lowercase")]
|
||||||
/// enum Command {
|
/// enum Command {
|
||||||
/// Text(String),
|
/// Text(String),
|
||||||
|
@ -118,9 +123,9 @@ pub use teloxide_macros::BotCommand;
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # #[cfg(feature = "macros")] {
|
/// # #[cfg(feature = "macros")] {
|
||||||
/// use teloxide::utils::command::BotCommand;
|
/// use teloxide::utils::command::BotCommands;
|
||||||
///
|
///
|
||||||
/// #[derive(BotCommand, PartialEq, Debug)]
|
/// #[derive(BotCommands, PartialEq, Debug)]
|
||||||
/// #[command(rename = "lowercase", parse_with = "split")]
|
/// #[command(rename = "lowercase", parse_with = "split")]
|
||||||
/// enum Command {
|
/// enum Command {
|
||||||
/// Nums(u8, u16, i32),
|
/// Nums(u8, u16, i32),
|
||||||
|
@ -138,9 +143,9 @@ pub use teloxide_macros::BotCommand;
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # #[cfg(feature = "macros")] {
|
/// # #[cfg(feature = "macros")] {
|
||||||
/// use teloxide::utils::command::BotCommand;
|
/// use teloxide::utils::command::BotCommands;
|
||||||
///
|
///
|
||||||
/// #[derive(BotCommand, PartialEq, Debug)]
|
/// #[derive(BotCommands, PartialEq, Debug)]
|
||||||
/// #[command(rename = "lowercase", parse_with = "split", separator = "|")]
|
/// #[command(rename = "lowercase", parse_with = "split", separator = "|")]
|
||||||
/// enum Command {
|
/// enum Command {
|
||||||
/// Nums(u8, u16, i32),
|
/// Nums(u8, u16, i32),
|
||||||
|
@ -171,7 +176,7 @@ pub use teloxide_macros::BotCommand;
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # #[cfg(feature = "macros")] {
|
/// # #[cfg(feature = "macros")] {
|
||||||
/// use teloxide::utils::command::{BotCommand, ParseError};
|
/// use teloxide::utils::command::{BotCommands, ParseError};
|
||||||
///
|
///
|
||||||
/// fn accept_two_digits(input: String) -> Result<(u8,), ParseError> {
|
/// fn accept_two_digits(input: String) -> Result<(u8,), ParseError> {
|
||||||
/// match input.len() {
|
/// match input.len() {
|
||||||
|
@ -183,7 +188,7 @@ pub use teloxide_macros::BotCommand;
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// #[derive(BotCommand, PartialEq, Debug)]
|
/// #[derive(BotCommands, PartialEq, Debug)]
|
||||||
/// #[command(rename = "lowercase")]
|
/// #[command(rename = "lowercase")]
|
||||||
/// enum Command {
|
/// enum Command {
|
||||||
/// #[command(parse_with = "accept_two_digits")]
|
/// #[command(parse_with = "accept_two_digits")]
|
||||||
|
@ -204,24 +209,41 @@ pub use teloxide_macros::BotCommand;
|
||||||
/// specific variant.
|
/// specific variant.
|
||||||
///
|
///
|
||||||
/// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html
|
/// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html
|
||||||
/// [`BotCommand`]: crate::utils::command::BotCommand
|
/// [`BotCommands`]: crate::utils::command::BotCommands
|
||||||
pub trait BotCommand: Sized {
|
pub trait BotCommands: Sized {
|
||||||
fn descriptions() -> String;
|
/// Parses a command.
|
||||||
fn parse<N>(s: &str, bot_name: N) -> Result<Self, ParseError>
|
///
|
||||||
|
/// `bot_username` is required to parse commands like
|
||||||
|
/// `/cmd@username_of_the_bot`.
|
||||||
|
fn parse<N>(s: &str, bot_username: N) -> Result<Self, ParseError>
|
||||||
where
|
where
|
||||||
N: Into<String>;
|
N: Into<String>;
|
||||||
|
|
||||||
|
/// Returns descriptions of the commands suitable to be shown to the user
|
||||||
|
/// (for example when `/help` command is used).
|
||||||
|
fn descriptions() -> CommandDescriptions<'static>;
|
||||||
|
|
||||||
|
/// Returns a vector of [`BotCommand`] that can be used with
|
||||||
|
/// [`set_my_commands`].
|
||||||
|
///
|
||||||
|
/// [`BotCommand`]: crate::types::BotCommand
|
||||||
|
/// [`set_my_commands`]: crate::requests::Requester::set_my_commands
|
||||||
|
fn bot_commands() -> Vec<BotCommand>;
|
||||||
|
|
||||||
|
/// Returns `PhantomData<Self>` that is used as a param of [`commands_repl`]
|
||||||
|
///
|
||||||
|
/// [`commands_repl`]: (crate::repls2::commands_repl)
|
||||||
fn ty() -> PhantomData<Self> {
|
fn ty() -> PhantomData<Self> {
|
||||||
PhantomData
|
PhantomData
|
||||||
}
|
}
|
||||||
fn bot_commands() -> Vec<crate::types::BotCommand>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type PrefixedBotCommand = String;
|
pub type PrefixedBotCommand = String;
|
||||||
pub type BotName = String;
|
pub type BotName = String;
|
||||||
|
|
||||||
/// Errors returned from [`BotCommand::parse`].
|
/// Errors returned from [`BotCommands::parse`].
|
||||||
///
|
///
|
||||||
/// [`BotCommand::parse`]: crate::utils::command::BotCommand::parse
|
/// [`BotCommands::parse`]: BotCommands::parse
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ParseError {
|
pub enum ParseError {
|
||||||
TooFewArguments {
|
TooFewArguments {
|
||||||
|
@ -247,28 +269,77 @@ pub enum ParseError {
|
||||||
Custom(Box<dyn Error + Send + Sync + 'static>),
|
Custom(Box<dyn Error + Send + Sync + 'static>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ParseError {
|
/// Command descriptions that can be shown to the user (e.g. as a part of
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
/// `/help` message)
|
||||||
match self {
|
///
|
||||||
ParseError::TooFewArguments { expected, found, message } => write!(
|
/// Most of the time you don't need to create this struct yourself as it's
|
||||||
f,
|
/// returned from [`BotCommands::descriptions`].
|
||||||
"Too few arguments (expected {}, found {}, message = '{}')",
|
#[derive(Debug, Clone)]
|
||||||
expected, found, message
|
pub struct CommandDescriptions<'a> {
|
||||||
),
|
global_description: Option<&'a str>,
|
||||||
ParseError::TooManyArguments { expected, found, message } => write!(
|
descriptions: &'a [CommandDescription<'a>],
|
||||||
f,
|
bot_username: Option<&'a str>,
|
||||||
"Too many arguments (expected {}, found {}, message = '{}')",
|
|
||||||
expected, found, message
|
|
||||||
),
|
|
||||||
ParseError::IncorrectFormat(e) => write!(f, "Incorrect format of command args: {}", e),
|
|
||||||
ParseError::UnknownCommand(e) => write!(f, "Unknown command: {}", e),
|
|
||||||
ParseError::WrongBotName(n) => write!(f, "Wrong bot name: {}", n),
|
|
||||||
ParseError::Custom(e) => write!(f, "{}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for ParseError {}
|
/// Description of a particular command, used in [`CommandDescriptions`].
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CommandDescription<'a> {
|
||||||
|
/// Prefix of the command, usually `/`.
|
||||||
|
pub prefix: &'a str,
|
||||||
|
/// The command itself, e.g. `start`.
|
||||||
|
pub command: &'a str,
|
||||||
|
/// Human-readable description of the command.
|
||||||
|
pub description: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CommandDescriptions<'a> {
|
||||||
|
/// Creates new [`CommandDescriptions`] from a list of command descriptions.
|
||||||
|
pub fn new(descriptions: &'a [CommandDescription<'a>]) -> Self {
|
||||||
|
Self { global_description: None, descriptions, bot_username: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the global description of these commands.
|
||||||
|
pub fn global_description(self, global_description: &'a str) -> Self {
|
||||||
|
Self { global_description: Some(global_description), ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the username of the bot.
|
||||||
|
///
|
||||||
|
/// After this method is called, returned instance of
|
||||||
|
/// [`CommandDescriptions`] will append `@bot_username` to all commands.
|
||||||
|
/// This is useful in groups, to disambiguate commands for different bots.
|
||||||
|
///
|
||||||
|
/// ## Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use teloxide::utils::command::{CommandDescription, CommandDescriptions};
|
||||||
|
///
|
||||||
|
/// let descriptions = CommandDescriptions::new(&[
|
||||||
|
/// CommandDescription { prefix: "/", command: "start", description: "start this bot" },
|
||||||
|
/// CommandDescription { prefix: "/", command: "help", description: "show this message" },
|
||||||
|
/// ]);
|
||||||
|
///
|
||||||
|
/// assert_eq!(descriptions.to_string(), "/start — start this bot\n/help — show this message");
|
||||||
|
/// assert_eq!(
|
||||||
|
/// descriptions.username("username_of_the_bot").to_string(),
|
||||||
|
/// "/start@username_of_the_bot — start this bot\n/help@username_of_the_bot — show this \
|
||||||
|
/// message"
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
pub fn username(self, bot_username: &'a str) -> Self {
|
||||||
|
Self { bot_username: Some(bot_username), ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the username of the bot.
|
||||||
|
///
|
||||||
|
/// This is the same as [`username`], but uses value returned from `get_me`
|
||||||
|
/// method to get the username.
|
||||||
|
///
|
||||||
|
/// [`username`]: self::CommandDescriptions::username
|
||||||
|
pub fn username_from_me(self, me: &'a Me) -> CommandDescriptions<'a> {
|
||||||
|
self.username(me.user.username.as_deref().expect("Bots must have usernames"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parses a string into a command with args.
|
/// Parses a string into a command with args.
|
||||||
///
|
///
|
||||||
|
@ -346,6 +417,68 @@ where
|
||||||
Some((command, words.collect()))
|
Some((command, words.collect()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for ParseError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
|
match self {
|
||||||
|
ParseError::TooFewArguments { expected, found, message } => write!(
|
||||||
|
f,
|
||||||
|
"Too few arguments (expected {}, found {}, message = '{}')",
|
||||||
|
expected, found, message
|
||||||
|
),
|
||||||
|
ParseError::TooManyArguments { expected, found, message } => write!(
|
||||||
|
f,
|
||||||
|
"Too many arguments (expected {}, found {}, message = '{}')",
|
||||||
|
expected, found, message
|
||||||
|
),
|
||||||
|
ParseError::IncorrectFormat(e) => write!(f, "Incorrect format of command args: {}", e),
|
||||||
|
ParseError::UnknownCommand(e) => write!(f, "Unknown command: {}", e),
|
||||||
|
ParseError::WrongBotName(n) => write!(f, "Wrong bot name: {}", n),
|
||||||
|
ParseError::Custom(e) => write!(f, "{}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ParseError {}
|
||||||
|
|
||||||
|
impl Display for CommandDescriptions<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if let Some(global_description) = self.global_description {
|
||||||
|
f.write_str(global_description)?;
|
||||||
|
f.write_str("\n\n")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut write = |&CommandDescription { prefix, command, description }, nls| {
|
||||||
|
if nls {
|
||||||
|
f.write_char('\n')?;
|
||||||
|
}
|
||||||
|
|
||||||
|
f.write_str(prefix)?;
|
||||||
|
f.write_str(command)?;
|
||||||
|
|
||||||
|
if let Some(username) = self.bot_username {
|
||||||
|
f.write_char('@')?;
|
||||||
|
f.write_str(username)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !description.is_empty() {
|
||||||
|
f.write_str(" — ")?;
|
||||||
|
f.write_str(description)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt::Result::Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(descr) = self.descriptions.first() {
|
||||||
|
write(descr, false)?;
|
||||||
|
for descr in &self.descriptions[1..] {
|
||||||
|
write(descr, true)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The rest of tests are integrational due to problems with macro expansion in
|
// The rest of tests are integrational due to problems with macro expansion in
|
||||||
// unit tests.
|
// unit tests.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -13,6 +13,8 @@ use tokio::sync::Notify;
|
||||||
use crate::dispatching::update_listeners::UpdateListener;
|
use crate::dispatching::update_listeners::UpdateListener;
|
||||||
|
|
||||||
/// A token which used to shutdown [`Dispatcher`].
|
/// A token which used to shutdown [`Dispatcher`].
|
||||||
|
///
|
||||||
|
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ShutdownToken {
|
pub struct ShutdownToken {
|
||||||
dispatcher_state: Arc<DispatcherState>,
|
dispatcher_state: Arc<DispatcherState>,
|
||||||
|
@ -21,6 +23,8 @@ pub struct ShutdownToken {
|
||||||
|
|
||||||
/// This error is returned from [`ShutdownToken::shutdown`] when trying to
|
/// This error is returned from [`ShutdownToken::shutdown`] when trying to
|
||||||
/// shutdown an idle [`Dispatcher`].
|
/// shutdown an idle [`Dispatcher`].
|
||||||
|
///
|
||||||
|
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct IdleShutdownError;
|
pub struct IdleShutdownError;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#![allow(clippy::nonstandard_macro_braces)]
|
#![allow(clippy::nonstandard_macro_braces)]
|
||||||
|
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
use teloxide::utils::command::{BotCommand, ParseError};
|
use teloxide::utils::command::{BotCommands, ParseError};
|
||||||
|
|
||||||
// We put tests here because macro expand in unit tests in module
|
// We put tests here because macro expand in unit tests in module
|
||||||
// teloxide::utils::command was a failure
|
// teloxide::utils::command was a failure
|
||||||
|
@ -10,7 +10,7 @@ use teloxide::utils::command::{BotCommand, ParseError};
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
fn parse_command_with_args() {
|
fn parse_command_with_args() {
|
||||||
#[derive(BotCommand, Debug, PartialEq)]
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
#[command(rename = "lowercase")]
|
#[command(rename = "lowercase")]
|
||||||
enum DefaultCommands {
|
enum DefaultCommands {
|
||||||
Start(String),
|
Start(String),
|
||||||
|
@ -26,7 +26,7 @@ fn parse_command_with_args() {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
fn parse_command_with_non_string_arg() {
|
fn parse_command_with_non_string_arg() {
|
||||||
#[derive(BotCommand, Debug, PartialEq)]
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
#[command(rename = "lowercase")]
|
#[command(rename = "lowercase")]
|
||||||
enum DefaultCommands {
|
enum DefaultCommands {
|
||||||
Start(i32),
|
Start(i32),
|
||||||
|
@ -42,7 +42,7 @@ fn parse_command_with_non_string_arg() {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
fn attribute_prefix() {
|
fn attribute_prefix() {
|
||||||
#[derive(BotCommand, Debug, PartialEq)]
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
#[command(rename = "lowercase")]
|
#[command(rename = "lowercase")]
|
||||||
enum DefaultCommands {
|
enum DefaultCommands {
|
||||||
#[command(prefix = "!")]
|
#[command(prefix = "!")]
|
||||||
|
@ -59,7 +59,7 @@ fn attribute_prefix() {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
fn many_attributes() {
|
fn many_attributes() {
|
||||||
#[derive(BotCommand, Debug, PartialEq)]
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
#[command(rename = "lowercase")]
|
#[command(rename = "lowercase")]
|
||||||
enum DefaultCommands {
|
enum DefaultCommands {
|
||||||
#[command(prefix = "!", description = "desc")]
|
#[command(prefix = "!", description = "desc")]
|
||||||
|
@ -68,13 +68,13 @@ fn many_attributes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(DefaultCommands::Start, DefaultCommands::parse("!start", "").unwrap());
|
assert_eq!(DefaultCommands::Start, DefaultCommands::parse("!start", "").unwrap());
|
||||||
assert_eq!(DefaultCommands::descriptions(), "!start - desc\n/help\n");
|
assert_eq!(DefaultCommands::descriptions().to_string(), "!start — desc\n/help");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
fn global_attributes() {
|
fn global_attributes() {
|
||||||
#[derive(BotCommand, Debug, PartialEq)]
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
#[command(prefix = "!", rename = "lowercase", description = "Bot commands")]
|
#[command(prefix = "!", rename = "lowercase", description = "Bot commands")]
|
||||||
enum DefaultCommands {
|
enum DefaultCommands {
|
||||||
#[command(prefix = "/")]
|
#[command(prefix = "/")]
|
||||||
|
@ -84,13 +84,13 @@ fn global_attributes() {
|
||||||
|
|
||||||
assert_eq!(DefaultCommands::Start, DefaultCommands::parse("/start", "MyNameBot").unwrap());
|
assert_eq!(DefaultCommands::Start, DefaultCommands::parse("/start", "MyNameBot").unwrap());
|
||||||
assert_eq!(DefaultCommands::Help, DefaultCommands::parse("!help", "MyNameBot").unwrap());
|
assert_eq!(DefaultCommands::Help, DefaultCommands::parse("!help", "MyNameBot").unwrap());
|
||||||
assert_eq!(DefaultCommands::descriptions(), "Bot commands\n/start\n!help\n");
|
assert_eq!(DefaultCommands::descriptions().to_string(), "Bot commands\n\n/start\n!help");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
fn parse_command_with_bot_name() {
|
fn parse_command_with_bot_name() {
|
||||||
#[derive(BotCommand, Debug, PartialEq)]
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
#[command(rename = "lowercase")]
|
#[command(rename = "lowercase")]
|
||||||
enum DefaultCommands {
|
enum DefaultCommands {
|
||||||
#[command(prefix = "/")]
|
#[command(prefix = "/")]
|
||||||
|
@ -107,7 +107,7 @@ fn parse_command_with_bot_name() {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
fn parse_with_split() {
|
fn parse_with_split() {
|
||||||
#[derive(BotCommand, Debug, PartialEq)]
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
#[command(rename = "lowercase")]
|
#[command(rename = "lowercase")]
|
||||||
#[command(parse_with = "split")]
|
#[command(parse_with = "split")]
|
||||||
enum DefaultCommands {
|
enum DefaultCommands {
|
||||||
|
@ -124,7 +124,7 @@ fn parse_with_split() {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
fn parse_with_split2() {
|
fn parse_with_split2() {
|
||||||
#[derive(BotCommand, Debug, PartialEq)]
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
#[command(rename = "lowercase")]
|
#[command(rename = "lowercase")]
|
||||||
#[command(parse_with = "split", separator = "|")]
|
#[command(parse_with = "split", separator = "|")]
|
||||||
enum DefaultCommands {
|
enum DefaultCommands {
|
||||||
|
@ -152,7 +152,7 @@ fn parse_custom_parser() {
|
||||||
.map_err(|_| ParseError::Custom("First argument must be a integer!".to_owned().into()))
|
.map_err(|_| ParseError::Custom("First argument must be a integer!".to_owned().into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(BotCommand, Debug, PartialEq)]
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
#[command(rename = "lowercase")]
|
#[command(rename = "lowercase")]
|
||||||
enum DefaultCommands {
|
enum DefaultCommands {
|
||||||
#[command(parse_with = "custom_parse_function")]
|
#[command(parse_with = "custom_parse_function")]
|
||||||
|
@ -169,7 +169,7 @@ fn parse_custom_parser() {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
fn parse_named_fields() {
|
fn parse_named_fields() {
|
||||||
#[derive(BotCommand, Debug, PartialEq)]
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
#[command(rename = "lowercase")]
|
#[command(rename = "lowercase")]
|
||||||
#[command(parse_with = "split")]
|
#[command(parse_with = "split")]
|
||||||
enum DefaultCommands {
|
enum DefaultCommands {
|
||||||
|
@ -186,7 +186,7 @@ fn parse_named_fields() {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
fn descriptions_off() {
|
fn descriptions_off() {
|
||||||
#[derive(BotCommand, Debug, PartialEq)]
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
#[command(rename = "lowercase")]
|
#[command(rename = "lowercase")]
|
||||||
enum DefaultCommands {
|
enum DefaultCommands {
|
||||||
#[command(description = "off")]
|
#[command(description = "off")]
|
||||||
|
@ -194,13 +194,13 @@ fn descriptions_off() {
|
||||||
Help,
|
Help,
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(DefaultCommands::descriptions(), "/help\n".to_owned());
|
assert_eq!(DefaultCommands::descriptions().to_string(), "/help".to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
fn rename_rules() {
|
fn rename_rules() {
|
||||||
#[derive(BotCommand, Debug, PartialEq)]
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
enum DefaultCommands {
|
enum DefaultCommands {
|
||||||
#[command(rename = "lowercase")]
|
#[command(rename = "lowercase")]
|
||||||
AaaAaa,
|
AaaAaa,
|
||||||
|
@ -230,7 +230,7 @@ fn rename_rules() {
|
||||||
assert_eq!(DefaultCommands::HhhHhh, DefaultCommands::parse("/HHH-HHH", "").unwrap());
|
assert_eq!(DefaultCommands::HhhHhh, DefaultCommands::parse("/HHH-HHH", "").unwrap());
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"/aaaaaa\n/BBBBBB\n/CccCcc\n/dddDdd\n/eee_eee\n/FFF_FFF\n/ggg-ggg\n/HHH-HHH\n",
|
"/aaaaaa\n/BBBBBB\n/CccCcc\n/dddDdd\n/eee_eee\n/FFF_FFF\n/ggg-ggg\n/HHH-HHH",
|
||||||
DefaultCommands::descriptions()
|
DefaultCommands::descriptions().to_string()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ use std::{
|
||||||
use teloxide::dispatching::dialogue::{RedisStorage, RedisStorageError, Serializer, Storage};
|
use teloxide::dispatching::dialogue::{RedisStorage, RedisStorageError, Serializer, Storage};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
#[cfg_attr(not(CI_REDIS), ignore)]
|
||||||
async fn test_redis_json() {
|
async fn test_redis_json() {
|
||||||
let storage = RedisStorage::open(
|
let storage = RedisStorage::open(
|
||||||
"redis://127.0.0.1:7777",
|
"redis://127.0.0.1:7777",
|
||||||
|
@ -16,6 +17,7 @@ async fn test_redis_json() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
#[cfg_attr(not(CI_REDIS), ignore)]
|
||||||
async fn test_redis_bincode() {
|
async fn test_redis_bincode() {
|
||||||
let storage = RedisStorage::open(
|
let storage = RedisStorage::open(
|
||||||
"redis://127.0.0.1:7778",
|
"redis://127.0.0.1:7778",
|
||||||
|
@ -27,6 +29,7 @@ async fn test_redis_bincode() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
#[cfg_attr(not(CI_REDIS), ignore)]
|
||||||
async fn test_redis_cbor() {
|
async fn test_redis_cbor() {
|
||||||
let storage = RedisStorage::open(
|
let storage = RedisStorage::open(
|
||||||
"redis://127.0.0.1:7779",
|
"redis://127.0.0.1:7779",
|
||||||
|
|
|
@ -1,36 +1,47 @@
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
|
fs,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use teloxide::dispatching::dialogue::{Serializer, SqliteStorage, SqliteStorageError, Storage};
|
use teloxide::dispatching::dialogue::{Serializer, SqliteStorage, SqliteStorageError, Storage};
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_sqlite_json() {
|
async fn test_sqlite_json() {
|
||||||
let storage =
|
fs::create_dir("./test_db1").unwrap();
|
||||||
SqliteStorage::open("./test_db1.sqlite", teloxide::dispatching::dialogue::serializer::Json)
|
let storage = SqliteStorage::open(
|
||||||
.await
|
"./test_db1/test_db1.sqlite",
|
||||||
.unwrap();
|
teloxide::dispatching::dialogue::serializer::Json,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
test_sqlite(storage).await;
|
test_sqlite(storage).await;
|
||||||
|
fs::remove_dir_all("./test_db1").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_sqlite_bincode() {
|
async fn test_sqlite_bincode() {
|
||||||
|
fs::create_dir("./test_db2").unwrap();
|
||||||
let storage = SqliteStorage::open(
|
let storage = SqliteStorage::open(
|
||||||
"./test_db2.sqlite",
|
"./test_db2/test_db2.sqlite",
|
||||||
teloxide::dispatching::dialogue::serializer::Bincode,
|
teloxide::dispatching::dialogue::serializer::Bincode,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
test_sqlite(storage).await;
|
test_sqlite(storage).await;
|
||||||
|
fs::remove_dir_all("./test_db2").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_sqlite_cbor() {
|
async fn test_sqlite_cbor() {
|
||||||
let storage =
|
fs::create_dir("./test_db3").unwrap();
|
||||||
SqliteStorage::open("./test_db3.sqlite", teloxide::dispatching::dialogue::serializer::Cbor)
|
let storage = SqliteStorage::open(
|
||||||
.await
|
"./test_db3/test_db3.sqlite",
|
||||||
.unwrap();
|
teloxide::dispatching::dialogue::serializer::Cbor,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
test_sqlite(storage).await;
|
test_sqlite(storage).await;
|
||||||
|
fs::remove_dir_all("./test_db3").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
type Dialogue = String;
|
type Dialogue = String;
|
||||||
|
|
Loading…
Reference in a new issue