From 87c8557e361c006e1e27bdda4c6c302dbff7d029 Mon Sep 17 00:00:00 2001 From: LasterAlex Date: Thu, 15 Aug 2024 23:01:26 +0300 Subject: [PATCH 1/6] Added deep linking example --- crates/teloxide/Cargo.toml | 4 + crates/teloxide/examples/deep_linking.rs | 139 +++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 crates/teloxide/examples/deep_linking.rs diff --git a/crates/teloxide/Cargo.toml b/crates/teloxide/Cargo.toml index 6315e147..c693fa82 100644 --- a/crates/teloxide/Cargo.toml +++ b/crates/teloxide/Cargo.toml @@ -194,6 +194,10 @@ required-features = [ "macros", ] +[[example]] +name = "deep_linking" +required-features = ["macros", "ctrlc_handler"] + [[example]] name = "dialogue" required-features = ["macros", "ctrlc_handler"] diff --git a/crates/teloxide/examples/deep_linking.rs b/crates/teloxide/examples/deep_linking.rs new file mode 100644 index 00000000..7e2a37e3 --- /dev/null +++ b/crates/teloxide/examples/deep_linking.rs @@ -0,0 +1,139 @@ +use dptree::{case, deps}; +use teloxide::{ + dispatching::dialogue::{self, InMemStorage}, + macros::BotCommands, + prelude::*, + types::Me, +}; + +pub type MyDialogue = Dialogue>; +pub type HandlerResult = Result<(), Box>; + +#[derive(Clone, PartialEq, Debug, Default)] +pub enum State { + #[default] + Start, + WriteToSomeone { + id: i64, + }, +} + +#[derive(BotCommands, Clone, Debug)] +#[command(rename_rule = "lowercase")] +pub enum StartCommand { + #[command()] + Start(String), // Because deep linking (links like https://t.me/some_bot?start=123456789) is the + // same as sending "/start 123456789", we can treat it as just an argument to a command + // + // https://core.telegram.org/bots/features#deep-linking +} + +#[tokio::main] +async fn main() { + pretty_env_logger::init(); + log::info!("Starting dialogue bot..."); + + let bot = Bot::from_env(); + + let handler = dialogue::enter::, State, _>() + .branch( + Update::filter_message() + .filter_command::() // Nessary to get cmd as an argument + .branch(case![StartCommand::Start(start)].endpoint(start)), + ) + .branch( + Update::filter_message() + .branch(case![State::WriteToSomeone { id }].endpoint(send_message)), + ); + + Dispatcher::builder(bot, handler) + .dependencies(deps![InMemStorage::::new()]) + .enable_ctrlc_handler() + .build() + .dispatch() + .await; +} + +pub async fn start( + bot: Bot, + dialogue: MyDialogue, + msg: Message, + cmd: StartCommand, + me: Me, +) -> HandlerResult { + // If you have multiple commands, this will need to become a match, not just a let + let StartCommand::Start(arg) = cmd; + + if arg.is_empty() { + // This means that it is just a regular link like https://t.me/some_bot, or a /start command + bot.send_message( + msg.chat.id, + format!( + "Hello!\n\nThis link allows anyone to message you secretly: {}?start={}", + me.tme_url(), + msg.chat.id.0 + ), + ) + .await?; + dialogue.exit().await?; + } else { + // And this means that the link is like this: https://t.me/some_bot?start=123456789 + match arg.parse::() { + Ok(id) => { + bot.send_message(msg.chat.id, "Send your message:").await?; + dialogue.update(State::WriteToSomeone { id }).await?; + } + Err(_) => { + bot.send_message(msg.chat.id, "Bad link!").await?; + dialogue.exit().await?; + } + } + } + Ok(()) +} + +pub async fn send_message( + bot: Bot, + id: i64, // Available from `State::WriteToSomeone`. + msg: Message, + dialogue: MyDialogue, + me: Me, +) -> HandlerResult { + match msg.text() { + Some(text) => { + // Trying to send a message to the user + let sent_result = bot + .send_message( + ChatId(id), + format!("You have a new message!\n\n{text}"), + ) + .parse_mode(teloxide::types::ParseMode::Html) + .await; + + // And if no error is returned, success! + if sent_result.is_ok() { + bot.send_message( + msg.chat.id, + format!( + "Message sent!\n\nYour link is: {}?start={}", + me.tme_url(), + msg.chat.id.0 + ), + ) + .await?; + } else { + bot.send_message( + msg.chat.id, + "Error sending message! Maybe user blocked the bot?", + ) + .await?; + } + dialogue.exit().await?; + } + None => { + bot.send_message(msg.chat.id, "This bot can send only text.") + .await?; + } + }; + Ok(()) +} From ab87451b62a80ff85aadaf6cbe88274767126db7 Mon Sep 17 00:00:00 2001 From: LasterAlex Date: Thu, 15 Aug 2024 23:25:08 +0300 Subject: [PATCH 2/6] Cargo fmt fix --- crates/teloxide/examples/deep_linking.rs | 25 +++++++++--------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/crates/teloxide/examples/deep_linking.rs b/crates/teloxide/examples/deep_linking.rs index 7e2a37e3..b8a6ceb5 100644 --- a/crates/teloxide/examples/deep_linking.rs +++ b/crates/teloxide/examples/deep_linking.rs @@ -22,10 +22,10 @@ pub enum State { #[command(rename_rule = "lowercase")] pub enum StartCommand { #[command()] - Start(String), // Because deep linking (links like https://t.me/some_bot?start=123456789) is the - // same as sending "/start 123456789", we can treat it as just an argument to a command - // - // https://core.telegram.org/bots/features#deep-linking + Start(String), /* Because deep linking (links like https://t.me/some_bot?start=123456789) is the + * same as sending "/start 123456789", we can treat it as just an argument to a command + * + * https://core.telegram.org/bots/features#deep-linking */ } #[tokio::main] @@ -61,7 +61,7 @@ pub async fn start( cmd: StartCommand, me: Me, ) -> HandlerResult { - // If you have multiple commands, this will need to become a match, not just a let + // If you have multiple commands, this will need to become a match let StartCommand::Start(arg) = cmd; if arg.is_empty() { @@ -103,10 +103,7 @@ pub async fn send_message( Some(text) => { // Trying to send a message to the user let sent_result = bot - .send_message( - ChatId(id), - format!("You have a new message!\n\n{text}"), - ) + .send_message(ChatId(id), format!("You have a new message!\n\n{text}")) .parse_mode(teloxide::types::ParseMode::Html) .await; @@ -122,17 +119,13 @@ pub async fn send_message( ) .await?; } else { - bot.send_message( - msg.chat.id, - "Error sending message! Maybe user blocked the bot?", - ) - .await?; + bot.send_message(msg.chat.id, "Error sending message! Maybe user blocked the bot?") + .await?; } dialogue.exit().await?; } None => { - bot.send_message(msg.chat.id, "This bot can send only text.") - .await?; + bot.send_message(msg.chat.id, "This bot can send only text.").await?; } }; Ok(()) From a4e4558eb10b12d02a066811db8142c91f7a245f Mon Sep 17 00:00:00 2001 From: LasterAlex Date: Thu, 15 Aug 2024 23:32:19 +0300 Subject: [PATCH 3/6] Another cargo fmt fix + Cargo.lock update for examples? --- Cargo.lock | 55 +++++++++++++++++++++--- crates/teloxide/examples/deep_linking.rs | 5 ++- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0386f50c..0a1963b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2208,7 +2208,7 @@ checksum = "20f34339676cdcab560c9a82300c4c2581f68b9369aedf0fae86f2ff9565ff3e" [[package]] name = "teloxide" -version = "0.12.2" +version = "0.13.0" dependencies = [ "aquamarine", "axum", @@ -2230,8 +2230,8 @@ dependencies = [ "serde_cbor", "serde_json", "sqlx", - "teloxide-core", - "teloxide-macros", + "teloxide-core 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "teloxide-macros 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror", "tokio", "tokio-stream", @@ -2243,7 +2243,7 @@ dependencies = [ [[package]] name = "teloxide-core" -version = "0.9.1" +version = "0.10.0" dependencies = [ "aho-corasick 0.7.20", "bitflags 1.3.2", @@ -2277,9 +2277,52 @@ dependencies = [ "xshell", ] +[[package]] +name = "teloxide-core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4308e2880a535d8c30e494d548af1deb573e1fc06f2574fdd01b8fccf7c801a" +dependencies = [ + "bitflags 1.3.2", + "bytes", + "chrono", + "derive_more", + "either", + "futures", + "log", + "mime", + "once_cell", + "pin-project", + "rc-box", + "reqwest", + "serde", + "serde_json", + "serde_with", + "take_mut", + "takecell", + "thiserror", + "tokio", + "tokio-util", + "url", + "uuid", + "vecrem", +] + [[package]] name = "teloxide-macros" -version = "0.7.1" +version = "0.8.0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "teloxide-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e2d33d809c3e7161a9ab18bedddf98821245014f0a78fa4d2c9430b2ec018c1" dependencies = [ "heck", "proc-macro2", @@ -2752,7 +2795,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/crates/teloxide/examples/deep_linking.rs b/crates/teloxide/examples/deep_linking.rs index b8a6ceb5..1b7502bd 100644 --- a/crates/teloxide/examples/deep_linking.rs +++ b/crates/teloxide/examples/deep_linking.rs @@ -22,8 +22,9 @@ pub enum State { #[command(rename_rule = "lowercase")] pub enum StartCommand { #[command()] - Start(String), /* Because deep linking (links like https://t.me/some_bot?start=123456789) is the - * same as sending "/start 123456789", we can treat it as just an argument to a command + Start(String), /* Because deep linking (links like https://t.me/some_bot?start=123456789) + * is the same as sending "/start 123456789", + * we can treat it as just an argument to a command * * https://core.telegram.org/bots/features#deep-linking */ } From 1181092bd513ebf69bea1a931481248462fee719 Mon Sep 17 00:00:00 2001 From: LasterAlex Date: Fri, 16 Aug 2024 14:18:02 +0300 Subject: [PATCH 4/6] Fixed review --- crates/teloxide/examples/deep_linking.rs | 46 +++++++++++++----------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/crates/teloxide/examples/deep_linking.rs b/crates/teloxide/examples/deep_linking.rs index 1b7502bd..22a8a035 100644 --- a/crates/teloxide/examples/deep_linking.rs +++ b/crates/teloxide/examples/deep_linking.rs @@ -1,3 +1,15 @@ +//! This example demonstrates how to use deep linking in Telegram +//! by making a simple anonymous message bot. +//! +//! Deep linking (links like https://t.me/some_bot?start=123456789) +//! is handled by telegram in the same way as just sending /start {argument}. +//! So, in the StartCommand enum we need to write Start(String) to get the argument, +//! just as in command.rs example. +//! +//! Also, deep linking is only supported with /start command! +//! "https://t.me/some_bot?argument=123456789" will not work +//! +//! https://core.telegram.org/bots/features#deep-linking use dptree::{case, deps}; use teloxide::{ dispatching::dialogue::{self, InMemStorage}, @@ -14,7 +26,7 @@ pub enum State { #[default] Start, WriteToSomeone { - id: i64, + id: ChatId, }, } @@ -22,24 +34,20 @@ pub enum State { #[command(rename_rule = "lowercase")] pub enum StartCommand { #[command()] - Start(String), /* Because deep linking (links like https://t.me/some_bot?start=123456789) - * is the same as sending "/start 123456789", - * we can treat it as just an argument to a command - * - * https://core.telegram.org/bots/features#deep-linking */ + Start(String), } #[tokio::main] async fn main() { pretty_env_logger::init(); - log::info!("Starting dialogue bot..."); + log::info!("Starting deep linking bot..."); let bot = Bot::from_env(); let handler = dialogue::enter::, State, _>() .branch( Update::filter_message() - .filter_command::() // Nessary to get cmd as an argument + .filter_command::() .branch(case![StartCommand::Start(start)].endpoint(start)), ) .branch( @@ -59,30 +67,28 @@ pub async fn start( bot: Bot, dialogue: MyDialogue, msg: Message, - cmd: StartCommand, + start: String, // Available from `case![StartCommand::Start(start)]` me: Me, ) -> HandlerResult { - // If you have multiple commands, this will need to become a match - let StartCommand::Start(arg) = cmd; - - if arg.is_empty() { + if start.is_empty() { // This means that it is just a regular link like https://t.me/some_bot, or a /start command bot.send_message( msg.chat.id, format!( "Hello!\n\nThis link allows anyone to message you secretly: {}?start={}", me.tme_url(), - msg.chat.id.0 + msg.chat.id ), ) .await?; dialogue.exit().await?; } else { - // And this means that the link is like this: https://t.me/some_bot?start=123456789 - match arg.parse::() { + // And this means that the link is like this: https://t.me/some_bot?start=123456789, + // or a /start 123456789 command + match start.parse::() { Ok(id) => { bot.send_message(msg.chat.id, "Send your message:").await?; - dialogue.update(State::WriteToSomeone { id }).await?; + dialogue.update(State::WriteToSomeone { id: ChatId(id) }).await?; } Err(_) => { bot.send_message(msg.chat.id, "Bad link!").await?; @@ -95,7 +101,7 @@ pub async fn start( pub async fn send_message( bot: Bot, - id: i64, // Available from `State::WriteToSomeone`. + id: ChatId, // Available from `State::WriteToSomeone` msg: Message, dialogue: MyDialogue, me: Me, @@ -104,7 +110,7 @@ pub async fn send_message( Some(text) => { // Trying to send a message to the user let sent_result = bot - .send_message(ChatId(id), format!("You have a new message!\n\n{text}")) + .send_message(id, format!("You have a new message!\n\n{text}")) .parse_mode(teloxide::types::ParseMode::Html) .await; @@ -115,7 +121,7 @@ pub async fn send_message( format!( "Message sent!\n\nYour link is: {}?start={}", me.tme_url(), - msg.chat.id.0 + msg.chat.id ), ) .await?; From da2ce2b116e1ca465ceda0fef127a4ad9697db59 Mon Sep 17 00:00:00 2001 From: LasterAlex Date: Fri, 16 Aug 2024 14:21:39 +0300 Subject: [PATCH 5/6] Fmt fix --- crates/teloxide/examples/deep_linking.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/teloxide/examples/deep_linking.rs b/crates/teloxide/examples/deep_linking.rs index 22a8a035..7bae00e7 100644 --- a/crates/teloxide/examples/deep_linking.rs +++ b/crates/teloxide/examples/deep_linking.rs @@ -3,8 +3,8 @@ //! //! Deep linking (links like https://t.me/some_bot?start=123456789) //! is handled by telegram in the same way as just sending /start {argument}. -//! So, in the StartCommand enum we need to write Start(String) to get the argument, -//! just as in command.rs example. +//! So, in the StartCommand enum we need to write Start(String) +//! to get the argument, just like in command.rs example. //! //! Also, deep linking is only supported with /start command! //! "https://t.me/some_bot?argument=123456789" will not work From 18920b546fb69d5fc03c0ca95be7374147831278 Mon Sep 17 00:00:00 2001 From: LasterAlex Date: Fri, 16 Aug 2024 22:02:04 +0300 Subject: [PATCH 6/6] Small fixes --- crates/teloxide/examples/deep_linking.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/teloxide/examples/deep_linking.rs b/crates/teloxide/examples/deep_linking.rs index 7bae00e7..d48b6a79 100644 --- a/crates/teloxide/examples/deep_linking.rs +++ b/crates/teloxide/examples/deep_linking.rs @@ -15,7 +15,7 @@ use teloxide::{ dispatching::dialogue::{self, InMemStorage}, macros::BotCommands, prelude::*, - types::Me, + types::{Me, ParseMode}, }; pub type MyDialogue = Dialogue>; @@ -33,7 +33,6 @@ pub enum State { #[derive(BotCommands, Clone, Debug)] #[command(rename_rule = "lowercase")] pub enum StartCommand { - #[command()] Start(String), } @@ -111,7 +110,7 @@ pub async fn send_message( // Trying to send a message to the user let sent_result = bot .send_message(id, format!("You have a new message!\n\n{text}")) - .parse_mode(teloxide::types::ParseMode::Html) + .parse_mode(ParseMode::Html) .await; // And if no error is returned, success!