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/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..d48b6a79 --- /dev/null +++ b/crates/teloxide/examples/deep_linking.rs @@ -0,0 +1,138 @@ +//! 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 like 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}, + macros::BotCommands, + prelude::*, + types::{Me, ParseMode}, +}; + +pub type MyDialogue = Dialogue>; +pub type HandlerResult = Result<(), Box>; + +#[derive(Clone, PartialEq, Debug, Default)] +pub enum State { + #[default] + Start, + WriteToSomeone { + id: ChatId, + }, +} + +#[derive(BotCommands, Clone, Debug)] +#[command(rename_rule = "lowercase")] +pub enum StartCommand { + Start(String), +} + +#[tokio::main] +async fn main() { + pretty_env_logger::init(); + log::info!("Starting deep linking bot..."); + + let bot = Bot::from_env(); + + let handler = dialogue::enter::, State, _>() + .branch( + Update::filter_message() + .filter_command::() + .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, + start: String, // Available from `case![StartCommand::Start(start)]` + me: Me, +) -> HandlerResult { + 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 + ), + ) + .await?; + dialogue.exit().await?; + } else { + // 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: ChatId(id) }).await?; + } + Err(_) => { + bot.send_message(msg.chat.id, "Bad link!").await?; + dialogue.exit().await?; + } + } + } + Ok(()) +} + +pub async fn send_message( + bot: Bot, + id: ChatId, // 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(id, format!("You have a new message!\n\n{text}")) + .parse_mode(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 + ), + ) + .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(()) +}