mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-22 14:35:36 +01:00
Merge pull request #1139 from LasterAlex/add-bot-mention-command-filter
Added mention_command filter
This commit is contained in:
commit
4df9487b6b
5 changed files with 236 additions and 8 deletions
|
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add `filter_boost_added` and `filter_reply_to_story` filters to `MessageFilterExt` trait
|
- Add `filter_boost_added` and `filter_reply_to_story` filters to `MessageFilterExt` trait
|
||||||
|
- Add `filter_mention_command` filter to `HandlerExt` trait ([issue #494](https://github.com/teloxide/teloxide/issues/494))
|
||||||
|
|
||||||
## 0.13.0 - 2024-08-16
|
## 0.13.0 - 2024-08-16
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
use teloxide::{
|
use teloxide::{
|
||||||
|
dispatching::HandlerExt,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
types::{Dice, ReplyParameters},
|
types::{Dice, ReplyParameters},
|
||||||
utils::command::BotCommands,
|
utils::command::BotCommands,
|
||||||
|
@ -52,12 +53,31 @@ async fn main() {
|
||||||
.branch(
|
.branch(
|
||||||
// Filtering allow you to filter updates by some condition.
|
// Filtering allow you to filter updates by some condition.
|
||||||
dptree::filter(|msg: Message| msg.chat.is_group() || msg.chat.is_supergroup())
|
dptree::filter(|msg: Message| msg.chat.is_group() || msg.chat.is_supergroup())
|
||||||
// An endpoint is the last update handler.
|
.branch(
|
||||||
.endpoint(|msg: Message, bot: Bot| async move {
|
// Filtering by mention allows to filter only `/repeat@my_bot` commands.
|
||||||
log::info!("Received a message from a group chat.");
|
// Use if you want to make sure that users refer specifically to your bot.
|
||||||
bot.send_message(msg.chat.id, "This is a group chat.").await?;
|
// Same as filter_command, the next handlers will receive a parsed
|
||||||
respond(())
|
// `GroupCommand`.
|
||||||
}),
|
dptree::entry().filter_mention_command::<GroupCommand>().endpoint(
|
||||||
|
|bot: Bot, msg: Message, cmd: GroupCommand| async move {
|
||||||
|
match cmd {
|
||||||
|
GroupCommand::Repeat { text } => {
|
||||||
|
bot.send_message(msg.chat.id, format!("You said: {text}"))
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.branch(
|
||||||
|
// An endpoint is the last update handler.
|
||||||
|
dptree::endpoint(|msg: Message, bot: 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(
|
.branch(
|
||||||
// There are some extension filtering functions on `Message`. The following filter will
|
// There are some extension filtering functions on `Message`. The following filter will
|
||||||
|
@ -116,6 +136,14 @@ enum MaintainerCommands {
|
||||||
Rand { from: u64, to: u64 },
|
Rand { from: u64, to: u64 },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Group commands
|
||||||
|
#[derive(BotCommands, Clone)]
|
||||||
|
#[command(rename_rule = "lowercase")]
|
||||||
|
enum GroupCommand {
|
||||||
|
/// Repeats a message
|
||||||
|
Repeat { text: String },
|
||||||
|
}
|
||||||
|
|
||||||
async fn simple_commands_handler(
|
async fn simple_commands_handler(
|
||||||
cfg: ConfigParameters,
|
cfg: ConfigParameters,
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
|
|
|
@ -227,4 +227,4 @@ pub use dispatcher::{Dispatcher, DispatcherBuilder, UpdateHandler};
|
||||||
pub use distribution::DefaultKey;
|
pub use distribution::DefaultKey;
|
||||||
pub use filter_ext::{MessageFilterExt, UpdateFilterExt};
|
pub use filter_ext::{MessageFilterExt, UpdateFilterExt};
|
||||||
pub use handler_description::DpHandlerDescription;
|
pub use handler_description::DpHandlerDescription;
|
||||||
pub use handler_ext::{filter_command, HandlerExt};
|
pub use handler_ext::{filter_command, filter_mention_command, HandlerExt};
|
||||||
|
|
|
@ -23,6 +23,18 @@ pub trait HandlerExt<Output> {
|
||||||
where
|
where
|
||||||
C: BotCommands + Send + Sync + 'static;
|
C: BotCommands + Send + Sync + 'static;
|
||||||
|
|
||||||
|
/// Returns a handler that accepts a parsed command `C` if the command
|
||||||
|
/// contains a bot mention, for example `/start@my_bot`.
|
||||||
|
///
|
||||||
|
/// ## Dependency requirements
|
||||||
|
///
|
||||||
|
/// - [`crate::types::Message`]
|
||||||
|
/// - [`crate::types::Me`]
|
||||||
|
#[must_use]
|
||||||
|
fn filter_mention_command<C>(self) -> Self
|
||||||
|
where
|
||||||
|
C: BotCommands + Send + Sync + 'static;
|
||||||
|
|
||||||
/// Passes [`Dialogue<D, S>`] and `D` as handler dependencies.
|
/// Passes [`Dialogue<D, S>`] and `D` as handler dependencies.
|
||||||
///
|
///
|
||||||
/// It does so by the following steps:
|
/// It does so by the following steps:
|
||||||
|
@ -61,6 +73,13 @@ where
|
||||||
self.chain(filter_command::<C, Output>())
|
self.chain(filter_command::<C, Output>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn filter_mention_command<C>(self) -> Self
|
||||||
|
where
|
||||||
|
C: BotCommands + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
self.chain(filter_mention_command::<C, Output>())
|
||||||
|
}
|
||||||
|
|
||||||
fn enter_dialogue<Upd, S, D>(self) -> Self
|
fn enter_dialogue<Upd, S, D>(self) -> Self
|
||||||
where
|
where
|
||||||
S: Storage<D> + ?Sized + Send + Sync + 'static,
|
S: Storage<D> + ?Sized + Send + Sync + 'static,
|
||||||
|
@ -93,3 +112,183 @@ where
|
||||||
message.text().and_then(|text| C::parse(text, &bot_name).ok())
|
message.text().and_then(|text| C::parse(text, &bot_name).ok())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a handler that accepts a parsed command `C` if the command
|
||||||
|
/// contains a bot mention, for example `/start@my_bot`.
|
||||||
|
///
|
||||||
|
/// A call to this function is the same as
|
||||||
|
/// `dptree::entry().filter_mention_command()`.
|
||||||
|
///
|
||||||
|
/// See [`HandlerExt::filter_mention_command`].
|
||||||
|
///
|
||||||
|
/// ## Dependency requirements
|
||||||
|
///
|
||||||
|
/// - [`crate::types::Message`]
|
||||||
|
/// - [`crate::types::Me`]
|
||||||
|
#[must_use]
|
||||||
|
pub fn filter_mention_command<C, Output>(
|
||||||
|
) -> Handler<'static, DependencyMap, Output, DpHandlerDescription>
|
||||||
|
where
|
||||||
|
C: BotCommands + Send + Sync + 'static,
|
||||||
|
Output: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
dptree::filter_map(move |message: Message, me: Me| {
|
||||||
|
let bot_name = me.user.username.expect("Bots must have a username");
|
||||||
|
|
||||||
|
let command = message.text().and_then(|text| C::parse(text, &bot_name).ok());
|
||||||
|
// If the parsing succeeds with a bot_name,
|
||||||
|
// but fails without - there is a mention
|
||||||
|
let is_username_required =
|
||||||
|
message.text().and_then(|text| C::parse(text, "").ok()).is_none();
|
||||||
|
|
||||||
|
if !is_username_required {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
command
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[cfg(feature = "macros")]
|
||||||
|
mod tests {
|
||||||
|
use crate::{self as teloxide, dispatching::UpdateFilterExt, utils::command::BotCommands};
|
||||||
|
use chrono::DateTime;
|
||||||
|
use dptree::deps;
|
||||||
|
use teloxide_core::types::{
|
||||||
|
Chat, ChatFullInfo, ChatId, ChatKind, ChatPrivate, LinkPreviewOptions, Me, MediaKind,
|
||||||
|
MediaText, Message, MessageCommon, MessageId, MessageKind, Update, UpdateId, UpdateKind,
|
||||||
|
User, UserId,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::HandlerExt;
|
||||||
|
|
||||||
|
#[derive(BotCommands, Clone)]
|
||||||
|
#[command(rename_rule = "lowercase")]
|
||||||
|
enum Cmd {
|
||||||
|
Test,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_update(text: String) -> Update {
|
||||||
|
let timestamp = 1_569_518_829;
|
||||||
|
let date = DateTime::from_timestamp(timestamp, 0).unwrap();
|
||||||
|
Update {
|
||||||
|
id: UpdateId(326_170_274),
|
||||||
|
kind: UpdateKind::Message(Message {
|
||||||
|
via_bot: None,
|
||||||
|
id: MessageId(5042),
|
||||||
|
thread_id: None,
|
||||||
|
from: Some(User {
|
||||||
|
id: UserId(109_998_024),
|
||||||
|
is_bot: false,
|
||||||
|
first_name: String::from("Laster"),
|
||||||
|
last_name: None,
|
||||||
|
username: Some(String::from("laster_alex")),
|
||||||
|
language_code: Some(String::from("en")),
|
||||||
|
is_premium: false,
|
||||||
|
added_to_attachment_menu: false,
|
||||||
|
}),
|
||||||
|
sender_chat: None,
|
||||||
|
is_topic_message: false,
|
||||||
|
date,
|
||||||
|
chat: Chat {
|
||||||
|
id: ChatId(109_998_024),
|
||||||
|
kind: ChatKind::Private(ChatPrivate {
|
||||||
|
username: Some(String::from("Laster")),
|
||||||
|
first_name: Some(String::from("laster_alex")),
|
||||||
|
last_name: None,
|
||||||
|
bio: None,
|
||||||
|
has_private_forwards: None,
|
||||||
|
has_restricted_voice_and_video_messages: None,
|
||||||
|
}),
|
||||||
|
photo: None,
|
||||||
|
available_reactions: None,
|
||||||
|
pinned_message: None,
|
||||||
|
message_auto_delete_time: None,
|
||||||
|
has_hidden_members: false,
|
||||||
|
has_aggressive_anti_spam_enabled: false,
|
||||||
|
chat_full_info: ChatFullInfo::default(),
|
||||||
|
},
|
||||||
|
kind: MessageKind::Common(MessageCommon {
|
||||||
|
reply_to_message: None,
|
||||||
|
forward_origin: None,
|
||||||
|
external_reply: None,
|
||||||
|
quote: None,
|
||||||
|
edit_date: None,
|
||||||
|
media_kind: MediaKind::Text(MediaText {
|
||||||
|
text,
|
||||||
|
entities: vec![],
|
||||||
|
link_preview_options: Some(LinkPreviewOptions {
|
||||||
|
is_disabled: true,
|
||||||
|
url: None,
|
||||||
|
prefer_small_media: false,
|
||||||
|
prefer_large_media: false,
|
||||||
|
show_above_text: false,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
reply_markup: None,
|
||||||
|
author_signature: None,
|
||||||
|
is_automatic_forward: false,
|
||||||
|
has_protected_content: false,
|
||||||
|
reply_to_story: None,
|
||||||
|
sender_boost_count: None,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_me() -> Me {
|
||||||
|
Me {
|
||||||
|
user: User {
|
||||||
|
id: UserId(42),
|
||||||
|
is_bot: true,
|
||||||
|
first_name: "First".to_owned(),
|
||||||
|
last_name: None,
|
||||||
|
username: Some("SomethingSomethingBot".to_owned()),
|
||||||
|
language_code: None,
|
||||||
|
is_premium: false,
|
||||||
|
added_to_attachment_menu: false,
|
||||||
|
},
|
||||||
|
can_join_groups: false,
|
||||||
|
can_read_all_group_messages: false,
|
||||||
|
supports_inline_queries: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_filter_command() {
|
||||||
|
let h = dptree::entry()
|
||||||
|
.branch(Update::filter_message().filter_command::<Cmd>().endpoint(|| async {}));
|
||||||
|
let me = make_me();
|
||||||
|
|
||||||
|
let update = make_update("/test@".to_owned() + me.username());
|
||||||
|
let result = h.dispatch(deps![update, me.clone()]).await;
|
||||||
|
assert!(result.is_break());
|
||||||
|
|
||||||
|
let update = make_update("/test@".to_owned() + "SomeOtherBot");
|
||||||
|
let result = h.dispatch(deps![update, me.clone()]).await;
|
||||||
|
assert!(result.is_continue());
|
||||||
|
|
||||||
|
let update = make_update("/test".to_owned());
|
||||||
|
let result = h.dispatch(deps![update, me.clone()]).await;
|
||||||
|
assert!(result.is_break());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_filter_mention_command() {
|
||||||
|
let h = dptree::entry()
|
||||||
|
.branch(Update::filter_message().filter_mention_command::<Cmd>().endpoint(|| async {}));
|
||||||
|
let me = make_me();
|
||||||
|
|
||||||
|
let update = make_update("/test@".to_owned() + me.username());
|
||||||
|
let result = h.dispatch(deps![update, me.clone()]).await;
|
||||||
|
assert!(result.is_break());
|
||||||
|
|
||||||
|
let update = make_update("/test@".to_owned() + "SomeOtherBot");
|
||||||
|
let result = h.dispatch(deps![update, me.clone()]).await;
|
||||||
|
assert!(result.is_continue());
|
||||||
|
|
||||||
|
let update = make_update("/test".to_owned());
|
||||||
|
let result = h.dispatch(deps![update, me.clone()]).await;
|
||||||
|
assert!(result.is_continue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -149,7 +149,7 @@ pub use teloxide_core::*;
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
pub use teloxide_macros as macros;
|
pub use teloxide_macros as macros;
|
||||||
|
|
||||||
pub use dispatching::filter_command;
|
pub use dispatching::{filter_command, filter_mention_command};
|
||||||
pub use dptree::{self, case as handler};
|
pub use dptree::{self, case as handler};
|
||||||
|
|
||||||
#[cfg(all(feature = "nightly", doctest))]
|
#[cfg(all(feature = "nightly", doctest))]
|
||||||
|
|
Loading…
Reference in a new issue