diff --git a/.gitignore b/.gitignore index 7c5b00ed..f2d88a0e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,9 @@ Cargo.lock .idea/ .vscode/ -examples/target +examples/ping_pong_bot/target +examples/dialogue_bot/target +examples/multiple_handlers_bot/target +examples/admin_bot/target +examples/guess_a_number_bot/target +examples/simple_commands_bot/target \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 23823539..9058d79a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,10 +20,14 @@ mime = "0.3.16" derive_more = "0.99.2" thiserror = "1.0.9" async-trait = "0.1.22" -duang = "0.1.2" futures = "0.3.1" pin-project = "0.4.6" serde_with_macros = "1.0.1" either = "1.5.3" -teloxide-macros = { path = "teloxide-macros" } \ No newline at end of file +teloxide-macros = { path = "teloxide-macros" } + +[dev-dependencies] +smart-default = "0.6.0" +rand = "0.7.3" +pretty_env_logger = "0.4.0" diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..7102117e --- /dev/null +++ b/examples/README.md @@ -0,0 +1,9 @@ +# Examples +Just enter the directory (for example, `cd dialogue_bot`) and execute `cargo run` to run an example. Don't forget to initialise the `TELOXIDE_TOKEN` environmental variable. + + - [ping_pong_bot](ping_pong_bot) - Answers "pong" to each incoming message. + - [simple_commands_bot](simple_commands_bot) - Shows how to deal with bot's commands. + - [guess_a_number_bot](guess_a_number_bot) - The "guess a number" game. + - [dialogue_bot](dialogue_bot) - Drive a dialogue with a user using a type-safe finite automaton. + - [admin_bot](admin_bot) - A bot, which can ban, kick, and mute on a command. + - [multiple_handlers_bot](multiple_handlers_bot) - Shows how multiple dispatcher's handlers relate to each other. \ No newline at end of file diff --git a/examples/admin_bot/Cargo.toml b/examples/admin_bot/Cargo.toml new file mode 100644 index 00000000..c9470f3d --- /dev/null +++ b/examples/admin_bot/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "admin_bot" +version = "0.1.0" +authors = ["p0lunin "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4.8" +tokio = "0.2.9" +pretty_env_logger = "0.4.0" +teloxide = { path = "../../" } + +[profile.release] +lto = true \ No newline at end of file diff --git a/examples/admin_bot/src/main.rs b/examples/admin_bot/src/main.rs new file mode 100644 index 00000000..9a891a5f --- /dev/null +++ b/examples/admin_bot/src/main.rs @@ -0,0 +1,203 @@ +// TODO: simplify this and use typed command variants (see https://github.com/teloxide/teloxide/issues/152). + +use teloxide::{ + prelude::*, types::ChatPermissions, utils::command::BotCommand, +}; + +// Derive BotCommand to parse text with a command into this enumeration. +// +// 1. rename = "lowercase" turns all the commands into lowercase letters. +// 2. `description = "..."` specifies a text before all the commands. +// +// That is, you can just call Command::descriptions() to get a description of +// your commands in this format: +// %GENERAL-DESCRIPTION% +// %PREFIX%%COMMAND% - %DESCRIPTION% +#[derive(BotCommand)] +#[command( + rename = "lowercase", + description = "Use commands in format /%command% %num% %unit%" +)] +enum Command { + #[command(description = "kick user from chat.")] + Kick, + #[command(description = "ban user in chat.")] + Ban, + #[command(description = "mute user in chat.")] + Mute, + + Help, +} + +// Calculates time of user restriction. +fn calc_restrict_time(num: i32, unit: &str) -> Result { + match unit { + "h" | "hours" => Ok(num * 3600), + "m" | "minutes" => Ok(num * 60), + "s" | "seconds" => Ok(num), + _ => Err("Allowed units: h, m, s"), + } +} + +// Parse arguments after a command. +fn parse_args(args: Vec<&str>) -> Result<(i32, &str), &str> { + let num = match args.get(0) { + Some(s) => s, + None => return Err("Use command in format /%command% %num% %unit%"), + }; + let unit = match args.get(1) { + Some(s) => s, + None => return Err("Use command in format /%command% %num% %unit%"), + }; + + match num.parse::() { + Ok(n) => Ok((n, unit)), + Err(_) => Err("input positive number!"), + } +} + +// Parse arguments into a user restriction duration. +fn parse_time_restrict(args: Vec<&str>) -> Result { + let (num, unit) = parse_args(args)?; + calc_restrict_time(num, unit) +} + +type Ctx = DispatcherHandlerCtx; + +// Mute a user with a replied message. +async fn mute_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { + match ctx.update.reply_to_message() { + Some(msg1) => match parse_time_restrict(args) { + // Mute user temporarily... + Ok(time) => { + ctx.bot + .restrict_chat_member( + ctx.update.chat_id(), + msg1.from().expect("Must be MessageKind::Common").id, + ChatPermissions::default(), + ) + .until_date(ctx.update.date + time) + .send() + .await?; + } + // ...or permanently + Err(_) => { + ctx.bot + .restrict_chat_member( + ctx.update.chat_id(), + msg1.from().unwrap().id, + ChatPermissions::default(), + ) + .send() + .await?; + } + }, + None => { + ctx.reply_to("Use this command in reply to another message") + .send() + .await?; + } + } + Ok(()) +} + +// Kick a user with a replied message. +async fn kick_user(ctx: &Ctx) -> Result<(), RequestError> { + match ctx.update.reply_to_message() { + Some(mes) => { + // bot.unban_chat_member can also kicks a user from a group chat. + ctx.bot + .unban_chat_member(ctx.update.chat_id(), mes.from().unwrap().id) + .send() + .await?; + } + None => { + ctx.reply_to("Use this command in reply to another message") + .send() + .await?; + } + } + Ok(()) +} + +// Ban a user with replied message. +async fn ban_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> { + match ctx.update.reply_to_message() { + Some(message) => match parse_time_restrict(args) { + // Mute user temporarily... + Ok(time) => { + ctx.bot + .kick_chat_member( + ctx.update.chat_id(), + message.from().expect("Must be MessageKind::Common").id, + ) + .until_date(ctx.update.date + time) + .send() + .await?; + } + // ...or permanently + Err(_) => { + ctx.bot + .kick_chat_member( + ctx.update.chat_id(), + message.from().unwrap().id, + ) + .send() + .await?; + } + }, + None => { + ctx.reply_to("Use this command in a reply to another message!") + .send() + .await?; + } + } + Ok(()) +} + +// Handle all messages. +async fn handle_command(ctx: Ctx) -> Result<(), RequestError> { + if ctx.update.chat.is_group() { + // The same as DispatcherHandlerResult::exit(Ok(())). If you have more + // handlers, use DispatcherHandlerResult::next(...) + return Ok(()); + } + + if let Some(text) = ctx.update.text() { + // Parse text into a command with args. + let (command, args): (Command, Vec<&str>) = match Command::parse(text) { + Some(tuple) => tuple, + None => return Ok(()), + }; + + match command { + Command::Help => { + ctx.answer(Command::descriptions()).send().await?; + } + Command::Kick => { + kick_user(&ctx).await?; + } + Command::Ban => { + ban_user(&ctx, args).await?; + } + Command::Mute => { + mute_user(&ctx, args).await?; + } + }; + } + + Ok(()) +} + +#[tokio::main] +async fn main() { + teloxide::enable_logging!(); + log::info!("Starting admin_bot!"); + + let bot = Bot::from_env(); + + Dispatcher::new(bot) + .message_handler(&handle_command) + .dispatch() + .await +} diff --git a/examples/dialogue_bot/Cargo.toml b/examples/dialogue_bot/Cargo.toml new file mode 100644 index 00000000..947470f0 --- /dev/null +++ b/examples/dialogue_bot/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "dialogue_bot" +version = "0.1.0" +authors = ["Temirkhan Myrzamadi "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4.8" +tokio = "0.2.9" +pretty_env_logger = "0.4.0" +smart-default = "0.6.0" +parse-display = "0.1.1" +teloxide = { path = "../../" } + +[profile.release] +lto = true \ No newline at end of file diff --git a/examples/dialogue_bot/src/main.rs b/examples/dialogue_bot/src/main.rs new file mode 100644 index 00000000..6a75d68c --- /dev/null +++ b/examples/dialogue_bot/src/main.rs @@ -0,0 +1,209 @@ +// This is a bot that asks your full name, your age, your favourite kind of +// music and sends all the gathered information back. +// +// # Example +// ``` +// - Let's start! First, what's your full name? +// - Luke Skywalker +// - What a wonderful name! Your age? +// - 26 +// - Good. Now choose your favourite music +// *A keyboard of music kinds is displayed* +// *You select Metal* +// - Metal +// - Fine. Your full name: Luke Skywalker, your age: 26, your favourite music: Metal +// ``` + +#![allow(clippy::trivial_regex)] + +#[macro_use] +extern crate smart_default; + +use teloxide::{ + prelude::*, + types::{KeyboardButton, ReplyKeyboardMarkup}, +}; + +use parse_display::{Display, FromStr}; + +// ============================================================================ +// [Favourite music kinds] +// ============================================================================ + +#[derive(Copy, Clone, Display, FromStr)] +enum FavouriteMusic { + Rock, + Metal, + Pop, + Other, +} + +impl FavouriteMusic { + fn markup() -> ReplyKeyboardMarkup { + ReplyKeyboardMarkup::default().append_row(vec![ + KeyboardButton::new("Rock"), + KeyboardButton::new("Metal"), + KeyboardButton::new("Pop"), + KeyboardButton::new("Other"), + ]) + } +} + +// ============================================================================ +// [A type-safe finite automaton] +// ============================================================================ + +#[derive(Clone)] +struct ReceiveAgeState { + full_name: String, +} + +#[derive(Clone)] +struct ReceiveFavouriteMusicState { + data: ReceiveAgeState, + age: u8, +} + +#[derive(Display)] +#[display( + "Your full name: {data.data.full_name}, your age: {data.age}, your \ + favourite music: {favourite_music}" +)] +struct ExitState { + data: ReceiveFavouriteMusicState, + favourite_music: FavouriteMusic, +} + +#[derive(SmartDefault)] +enum Dialogue { + #[default] + Start, + ReceiveFullName, + ReceiveAge(ReceiveAgeState), + ReceiveFavouriteMusic(ReceiveFavouriteMusicState), +} + +// ============================================================================ +// [Control a dialogue] +// ============================================================================ + +type Ctx = DialogueHandlerCtx; +type Res = Result, RequestError>; + +async fn start(ctx: Ctx<()>) -> Res { + ctx.answer("Let's start! First, what's your full name?") + .send() + .await?; + next(Dialogue::ReceiveFullName) +} + +async fn full_name(ctx: Ctx<()>) -> Res { + match ctx.update.text() { + None => { + ctx.answer("Please, send me a text message!").send().await?; + next(Dialogue::ReceiveFullName) + } + Some(full_name) => { + ctx.answer("What a wonderful name! Your age?") + .send() + .await?; + next(Dialogue::ReceiveAge(ReceiveAgeState { + full_name: full_name.to_owned(), + })) + } + } +} + +async fn age(ctx: Ctx) -> Res { + match ctx.update.text().unwrap().parse() { + Ok(age) => { + ctx.answer("Good. Now choose your favourite music:") + .reply_markup(FavouriteMusic::markup()) + .send() + .await?; + next(Dialogue::ReceiveFavouriteMusic( + ReceiveFavouriteMusicState { + data: ctx.dialogue, + age, + }, + )) + } + Err(_) => { + ctx.answer("Oh, please, enter a number!").send().await?; + next(Dialogue::ReceiveAge(ctx.dialogue)) + } + } +} + +async fn favourite_music(ctx: Ctx) -> Res { + match ctx.update.text().unwrap().parse() { + Ok(favourite_music) => { + ctx.answer(format!( + "Fine. {}", + ExitState { + data: ctx.dialogue.clone(), + favourite_music + } + )) + .send() + .await?; + exit() + } + Err(_) => { + ctx.answer("Oh, please, enter from the keyboard!") + .send() + .await?; + next(Dialogue::ReceiveFavouriteMusic(ctx.dialogue)) + } + } +} + +async fn handle_message(ctx: Ctx) -> Res { + match ctx { + DialogueHandlerCtx { + bot, + update, + dialogue: Dialogue::Start, + } => start(DialogueHandlerCtx::new(bot, update, ())).await, + DialogueHandlerCtx { + bot, + update, + dialogue: Dialogue::ReceiveFullName, + } => full_name(DialogueHandlerCtx::new(bot, update, ())).await, + DialogueHandlerCtx { + bot, + update, + dialogue: Dialogue::ReceiveAge(s), + } => age(DialogueHandlerCtx::new(bot, update, s)).await, + DialogueHandlerCtx { + bot, + update, + dialogue: Dialogue::ReceiveFavouriteMusic(s), + } => favourite_music(DialogueHandlerCtx::new(bot, update, s)).await, + } +} + +// ============================================================================ +// [Run!] +// ============================================================================ + +#[tokio::main] +async fn main() { + run().await; +} + +async fn run() { + teloxide::enable_logging!(); + log::info!("Starting dialogue_bot!"); + + let bot = Bot::from_env(); + + Dispatcher::new(bot) + .message_handler(&DialogueDispatcher::new(|ctx| async move { + handle_message(ctx) + .await + .expect("Something wrong with the bot!") + })) + .dispatch() + .await; +} diff --git a/examples/guess_a_number_bot/Cargo.toml b/examples/guess_a_number_bot/Cargo.toml new file mode 100644 index 00000000..c81ef20f --- /dev/null +++ b/examples/guess_a_number_bot/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "guess_a_number_bot" +version = "0.1.0" +authors = ["Temirkhan Myrzamadi "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4.8" +tokio = "0.2.9" +smart-default = "0.6.0" +rand = "0.7.3" +pretty_env_logger = "0.4.0" +teloxide = { path = "../../" } \ No newline at end of file diff --git a/examples/guess_a_number_bot/src/main.rs b/examples/guess_a_number_bot/src/main.rs new file mode 100644 index 00000000..2e9d573c --- /dev/null +++ b/examples/guess_a_number_bot/src/main.rs @@ -0,0 +1,116 @@ +// This is a guess-a-number game! +// +// # Example +// ``` +// - Hello +// - Let's play a game! Guess a number from 1 to 10 (inclusively). +// - 4 +// - No. +// - 3 +// - No. +// - Blablabla +// - Oh, please, send me a text message! +// - 111 +// - Oh, please, send me a number in the range [1; 10]! +// - 5 +// - Congratulations! You won! +// ``` + +#[macro_use] +extern crate smart_default; + +use teloxide::prelude::*; + +use rand::{thread_rng, Rng}; + +// ============================================================================ +// [A type-safe finite automaton] +// ============================================================================ + +#[derive(SmartDefault)] +enum Dialogue { + #[default] + Start, + ReceiveAttempt(u8), +} + +// ============================================================================ +// [Control a dialogue] +// ============================================================================ + +async fn handle_message( + ctx: DialogueHandlerCtx, +) -> Result, RequestError> { + match ctx.dialogue { + Dialogue::Start => { + ctx.answer( + "Let's play a game! Guess a number from 1 to 10 (inclusively).", + ) + .send() + .await?; + next(Dialogue::ReceiveAttempt(thread_rng().gen_range(1, 11))) + } + Dialogue::ReceiveAttempt(secret) => match ctx.update.text() { + None => { + ctx.answer("Oh, please, send me a text message!") + .send() + .await?; + next(ctx.dialogue) + } + Some(text) => match text.parse::() { + Ok(attempt) => match attempt { + x if !(1..=10).contains(&x) => { + ctx.answer( + "Oh, please, send me a number in the range [1; \ + 10]!", + ) + .send() + .await?; + next(ctx.dialogue) + } + x if x == secret => { + ctx.answer("Congratulations! You won!").send().await?; + exit() + } + _ => { + ctx.answer("No.").send().await?; + next(ctx.dialogue) + } + }, + Err(_) => { + ctx.answer( + "Oh, please, send me a number in the range [1; 10]!", + ) + .send() + .await?; + next(ctx.dialogue) + } + }, + }, + } +} + +// ============================================================================ +// [Run!] +// ============================================================================ + +#[tokio::main] +async fn main() { + run().await; +} + +async fn run() { + teloxide::enable_logging!(); + log::info!("Starting guess_a_number_bot!"); + + let bot = Bot::from_env(); + + Dispatcher::new(bot) + .message_handler(&DialogueDispatcher::new(|ctx| async move { + handle_message(ctx) + .await + .expect("Something wrong with the bot!") + })) + .dispatch() + .await; +} diff --git a/examples/multiple_handlers_bot/Cargo.toml b/examples/multiple_handlers_bot/Cargo.toml new file mode 100644 index 00000000..89a1b61a --- /dev/null +++ b/examples/multiple_handlers_bot/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "multiple_handlers_bot" +version = "0.1.0" +authors = ["Temirkhan Myrzamadi "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4.8" +tokio = "0.2.9" +pretty_env_logger = "0.4.0" +teloxide = { path = "../../" } \ No newline at end of file diff --git a/examples/multiple_handlers_bot/src/main.rs b/examples/multiple_handlers_bot/src/main.rs new file mode 100644 index 00000000..c09353e5 --- /dev/null +++ b/examples/multiple_handlers_bot/src/main.rs @@ -0,0 +1,49 @@ +// This example demonstrates the ability of Dispatcher to deal with multiple +// handlers. + +use teloxide::prelude::*; + +#[tokio::main] +async fn main() { + run().await; +} + +async fn run() { + teloxide::enable_logging!(); + log::info!("Starting multiple_handlers_bot!"); + + let bot = Bot::from_env(); + + // Create a dispatcher with multiple handlers of different types. This will + // print One! and Two! on every incoming UpdateKind::Message. + Dispatcher::::new(bot) + // This is the first UpdateKind::Message handler, which will be called + // after the Update handler below. + .message_handler(&|ctx: DispatcherHandlerCtx| async move { + log::info!("Two!"); + DispatcherHandlerResult::next(ctx.update, Ok(())) + }) + // Remember: handler of Update are called first. + .update_handler(&|ctx: DispatcherHandlerCtx| async move { + log::info!("One!"); + DispatcherHandlerResult::next(ctx.update, Ok(())) + }) + // This handler will be called right after the first UpdateKind::Message + // handler, because it is registered after. + .message_handler(&|_ctx: DispatcherHandlerCtx| async move { + // The same as DispatcherHandlerResult::exit(Ok(())) + Ok(()) + }) + // This handler will never be called, because the UpdateKind::Message + // handler above terminates the pipeline. + .message_handler(&|ctx: DispatcherHandlerCtx| async move { + log::info!("This will never be printed!"); + DispatcherHandlerResult::next(ctx.update, Ok(())) + }) + .dispatch() + .await; + + // Note: if this bot receive, for example, UpdateKind::ChannelPost, it will + // only print "One!", because the UpdateKind::Message handlers will not be + // called. +} diff --git a/examples/ping_pong_bot.rs b/examples/ping_pong_bot.rs deleted file mode 100644 index 8948dada..00000000 --- a/examples/ping_pong_bot.rs +++ /dev/null @@ -1,34 +0,0 @@ -use futures::stream::StreamExt; -use teloxide::{ - dispatching::{ - chat::{ChatUpdate, ChatUpdateKind, Dispatcher}, - update_listeners::polling_default, - SessionState, - }, - requests::Request, - Bot, -}; - -#[tokio::main] -async fn main() { - let bot = &Bot::new("1061598315:AAErEDodTsrqD3UxA_EvFyEfXbKA6DT25G0"); - let mut updater = Box::pin(polling_default(bot)); - let handler = |_, upd: ChatUpdate| async move { - if let ChatUpdateKind::Message(m) = upd.kind { - let msg = bot.send_message(m.chat.id, "pong"); - msg.send().await.unwrap(); - } - SessionState::Continue(()) - }; - let mut dp = Dispatcher::<'_, (), _>::new(handler); - println!("Starting the message handler."); - loop { - let u = updater.next().await.unwrap(); - match u { - Err(e) => eprintln!("Error: {}", e), - Ok(u) => { - let _ = dp.dispatch(u).await; - } - } - } -} diff --git a/examples/ping_pong_bot/Cargo.toml b/examples/ping_pong_bot/Cargo.toml new file mode 100644 index 00000000..73a9d9ed --- /dev/null +++ b/examples/ping_pong_bot/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "ping_pong_bot" +version = "0.1.0" +authors = ["Temirkhan Myrzamadi "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4.8" +tokio = "0.2.9" +pretty_env_logger = "0.4.0" +teloxide = { path = "../../" } + +[profile.release] +lto = true \ No newline at end of file diff --git a/examples/ping_pong_bot/src/main.rs b/examples/ping_pong_bot/src/main.rs new file mode 100644 index 00000000..e731200c --- /dev/null +++ b/examples/ping_pong_bot/src/main.rs @@ -0,0 +1,23 @@ +use teloxide::prelude::*; + +#[tokio::main] +async fn main() { + run().await; +} + +async fn run() { + teloxide::enable_logging!(); + log::info!("Starting ping_pong_bot!"); + + let bot = Bot::from_env(); + + // Create a dispatcher with a single message handler that answers "pong" to + // each incoming message. + Dispatcher::::new(bot) + .message_handler(&|ctx: DispatcherHandlerCtx| async move { + ctx.answer("pong").send().await?; + Ok(()) + }) + .dispatch() + .await; +} diff --git a/examples/simple_commands_bot/Cargo.toml b/examples/simple_commands_bot/Cargo.toml new file mode 100644 index 00000000..6baf6eb8 --- /dev/null +++ b/examples/simple_commands_bot/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "simple_commands_bot" +version = "0.1.0" +authors = ["Temirkhan Myrzamadi "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4.8" +tokio = "0.2.9" +rand = "0.7.3" +pretty_env_logger = "0.4.0" +teloxide = { path = "../../" } diff --git a/examples/simple_commands_bot/src/main.rs b/examples/simple_commands_bot/src/main.rs new file mode 100644 index 00000000..3d6525c5 --- /dev/null +++ b/examples/simple_commands_bot/src/main.rs @@ -0,0 +1,63 @@ +use teloxide::{prelude::*, utils::command::BotCommand}; + +use rand::{thread_rng, Rng}; + +#[derive(BotCommand)] +#[command(rename = "lowercase", description = "These commands are supported:")] +enum Command { + #[command(description = "display this text.")] + Help, + #[command(description = "be a cat.")] + Meow, + #[command(description = "generate a random number within [0; 1).")] + Generate, +} + +async fn handle_command( + ctx: DispatcherHandlerCtx, +) -> Result<(), RequestError> { + let text = match ctx.update.text() { + Some(text) => text, + None => { + log::info!("Received a message, but not text."); + return Ok(()); + } + }; + + let command = match Command::parse(text) { + Some((command, _)) => command, + None => { + log::info!("Received a text message, but not a command."); + return Ok(()); + } + }; + + match command { + Command::Help => ctx.answer(Command::descriptions()).send().await?, + Command::Generate => { + ctx.answer(thread_rng().gen_range(0.0, 1.0).to_string()) + .send() + .await? + } + Command::Meow => ctx.answer("I am a cat! Meow!").send().await?, + }; + + Ok(()) +} + +#[tokio::main] +async fn main() { + run().await; +} + +async fn run() { + teloxide::enable_logging!(); + log::info!("Starting simple_commands_bot!"); + + let bot = Bot::from_env(); + + Dispatcher::::new(bot) + .message_handler(&handle_command) + .dispatch() + .await; +} diff --git a/src/bot/api.rs b/src/bot/api.rs index ff3a8b88..a1058d49 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -25,6 +25,7 @@ use crate::{ }, Bot, }; +use std::sync::Arc; impl Bot { /// Use this method to receive incoming updates using long polling ([wiki]). @@ -37,8 +38,8 @@ impl Bot { /// [The official docs](https://core.telegram.org/bots/api#getupdates). /// /// [wiki]: https://en.wikipedia.org/wiki/Push_technology#Long_polling - pub fn get_updates(&self) -> GetUpdates { - GetUpdates::new(self) + pub fn get_updates(self: &Arc) -> GetUpdates { + GetUpdates::new(Arc::clone(self)) } /// Use this method to specify a url and receive incoming updates via an @@ -62,11 +63,11 @@ impl Bot { /// Use an empty string to remove webhook integration. /// /// [`Update`]: crate::types::Update - pub fn set_webhook(&self, url: U) -> SetWebhook + pub fn set_webhook(self: &Arc, url: U) -> SetWebhook where U: Into, { - SetWebhook::new(self, url) + SetWebhook::new(Arc::clone(self), url) } /// Use this method to remove webhook integration if you decide to switch @@ -75,8 +76,8 @@ impl Bot { /// [The official docs](https://core.telegram.org/bots/api#deletewebhook). /// /// [Bot::get_updates]: crate::Bot::get_updates - pub fn delete_webhook(&self) -> DeleteWebhook { - DeleteWebhook::new(self) + pub fn delete_webhook(self: &Arc) -> DeleteWebhook { + DeleteWebhook::new(Arc::clone(self)) } /// Use this method to get current webhook status. @@ -87,16 +88,16 @@ impl Bot { /// [The official docs](https://core.telegram.org/bots/api#getwebhookinfo). /// /// [`Bot::get_updates`]: crate::Bot::get_updates - pub fn get_webhook_info(&self) -> GetWebhookInfo { - GetWebhookInfo::new(self) + pub fn get_webhook_info(self: &Arc) -> GetWebhookInfo { + GetWebhookInfo::new(Arc::clone(self)) } /// A simple method for testing your bot's auth token. Requires no /// parameters. /// /// [The official docs](https://core.telegram.org/bots/api#getme). - pub fn get_me(&self) -> GetMe { - GetMe::new(self) + pub fn get_me(self: &Arc) -> GetMe { + GetMe::new(Arc::clone(self)) } /// Use this method to send text messages. @@ -107,12 +108,16 @@ impl Bot { /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). /// - `text`: Text of the message to be sent. - pub fn send_message(&self, chat_id: C, text: T) -> SendMessage + pub fn send_message( + self: &Arc, + chat_id: C, + text: T, + ) -> SendMessage where C: Into, T: Into, { - SendMessage::new(self, chat_id, text) + SendMessage::new(Arc::clone(self), chat_id, text) } /// Use this method to forward messages of any kind. @@ -130,7 +135,7 @@ impl Bot { /// /// [`from_chat_id`]: ForwardMessage::from_chat_id pub fn forward_message( - &self, + self: &Arc, chat_id: C, from_chat_id: F, message_id: i32, @@ -139,7 +144,7 @@ impl Bot { C: Into, F: Into, { - ForwardMessage::new(self, chat_id, from_chat_id, message_id) + ForwardMessage::new(Arc::clone(self), chat_id, from_chat_id, message_id) } /// Use this method to send photos. @@ -161,11 +166,15 @@ impl Bot { /// [`InputFile::FileId`]: crate::types::InputFile::FileId /// /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn send_photo(&self, chat_id: C, photo: InputFile) -> SendPhoto + pub fn send_photo( + self: &Arc, + chat_id: C, + photo: InputFile, + ) -> SendPhoto where C: Into, { - SendPhoto::new(self, chat_id, photo) + SendPhoto::new(Arc::clone(self), chat_id, photo) } /// @@ -173,11 +182,15 @@ impl Bot { /// # Params /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). - pub fn send_audio(&self, chat_id: C, audio: InputFile) -> SendAudio + pub fn send_audio( + self: &Arc, + chat_id: C, + audio: InputFile, + ) -> SendAudio where C: Into, { - SendAudio::new(self, chat_id, audio) + SendAudio::new(Arc::clone(self), chat_id, audio) } /// Use this method to send general files. @@ -199,14 +212,14 @@ impl Bot { /// /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files pub fn send_document( - &self, + self: &Arc, chat_id: C, document: InputFile, ) -> SendDocument where C: Into, { - SendDocument::new(self, chat_id, document) + SendDocument::new(Arc::clone(self), chat_id, document) } /// Use this method to send video files, Telegram clients support mp4 videos @@ -230,11 +243,15 @@ impl Bot { /// [`InputFile::File`]: crate::types::InputFile::File /// [`InputFile::Url`]: crate::types::InputFile::Url /// [`InputFile::FileId`]: crate::types::InputFile::FileId - pub fn send_video(&self, chat_id: C, video: InputFile) -> SendVideo + pub fn send_video( + self: &Arc, + chat_id: C, + video: InputFile, + ) -> SendVideo where C: Into, { - SendVideo::new(self, chat_id, video) + SendVideo::new(Arc::clone(self), chat_id, video) } /// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video @@ -250,14 +267,14 @@ impl Bot { /// target supergroup or channel (in the format `@channelusername`). /// - `animation`: Animation to send. pub fn send_animation( - &self, + self: &Arc, chat_id: C, animation: InputFile, ) -> SendAnimation where C: Into, { - SendAnimation::new(self, chat_id, animation) + SendAnimation::new(Arc::clone(self), chat_id, animation) } /// Use this method to send audio files, if you want Telegram clients to @@ -287,11 +304,15 @@ impl Bot { /// [`InputFile::Url`]: crate::types::InputFile::Url /// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn send_voice(&self, chat_id: C, voice: InputFile) -> SendVoice + pub fn send_voice( + self: &Arc, + chat_id: C, + voice: InputFile, + ) -> SendVoice where C: Into, { - SendVoice::new(self, chat_id, voice) + SendVoice::new(Arc::clone(self), chat_id, voice) } /// As of [v.4.0], Telegram clients support rounded square mp4 videos of up @@ -315,14 +336,14 @@ impl Bot { /// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files pub fn send_video_note( - &self, + self: &Arc, chat_id: C, video_note: InputFile, ) -> SendVideoNote where C: Into, { - SendVideoNote::new(self, chat_id, video_note) + SendVideoNote::new(Arc::clone(self), chat_id, video_note) } /// Use this method to send a group of photos or videos as an album. @@ -334,12 +355,16 @@ impl Bot { /// target supergroup or channel (in the format `@channelusername`). /// - `media`: A JSON-serialized array describing photos and videos to be /// sent, must include 2–10 items. - pub fn send_media_group(&self, chat_id: C, media: M) -> SendMediaGroup + pub fn send_media_group( + self: &Arc, + chat_id: C, + media: M, + ) -> SendMediaGroup where C: Into, M: Into>, { - SendMediaGroup::new(self, chat_id, media) + SendMediaGroup::new(Arc::clone(self), chat_id, media) } /// Use this method to send point on the map. @@ -352,7 +377,7 @@ impl Bot { /// - `latitude`: Latitude of the location. /// - `longitude`: Latitude of the location. pub fn send_location( - &self, + self: &Arc, chat_id: C, latitude: f32, longitude: f32, @@ -360,7 +385,7 @@ impl Bot { where C: Into, { - SendLocation::new(self, chat_id, latitude, longitude) + SendLocation::new(Arc::clone(self), chat_id, latitude, longitude) } /// Use this method to edit live location messages. @@ -379,13 +404,13 @@ impl Bot { /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True pub fn edit_message_live_location( - &self, + self: &Arc, chat_or_inline_message: ChatOrInlineMessage, latitude: f32, longitude: f32, ) -> EditMessageLiveLocation { EditMessageLiveLocation::new( - self, + Arc::clone(self), chat_or_inline_message, latitude, longitude, @@ -403,10 +428,10 @@ impl Bot { /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True pub fn stop_message_live_location( - &self, + self: &Arc, chat_or_inline_message: ChatOrInlineMessage, ) -> StopMessageLiveLocation { - StopMessageLiveLocation::new(self, chat_or_inline_message) + StopMessageLiveLocation::new(Arc::clone(self), chat_or_inline_message) } /// Use this method to send information about a venue. @@ -421,7 +446,7 @@ impl Bot { /// - `title`: Name of the venue. /// - `address`: Address of the venue. pub fn send_venue( - &self, + self: &Arc, chat_id: C, latitude: f32, longitude: f32, @@ -433,7 +458,14 @@ impl Bot { T: Into, A: Into, { - SendVenue::new(self, chat_id, latitude, longitude, title, address) + SendVenue::new( + Arc::clone(self), + chat_id, + latitude, + longitude, + title, + address, + ) } /// Use this method to send phone contacts. @@ -447,7 +479,7 @@ impl Bot { /// - `phone_number`: Contact's phone number. /// - `first_name`: Contact's first name. pub fn send_contact( - &self, + self: &Arc, chat_id: C, phone_number: P, first_name: F, @@ -457,7 +489,7 @@ impl Bot { P: Into, F: Into, { - SendContact::new(self, chat_id, phone_number, first_name) + SendContact::new(Arc::clone(self), chat_id, phone_number, first_name) } /// Use this method to send a native poll. A native poll can't be sent to a @@ -473,7 +505,7 @@ impl Bot { /// - `options`: List of answer options, 2-10 strings 1-100 characters /// each. pub fn send_poll( - &self, + self: &Arc, chat_id: C, question: Q, options: O, @@ -483,7 +515,7 @@ impl Bot { Q: Into, O: Into>, { - SendPoll::new(self, chat_id, question, options) + SendPoll::new(Arc::clone(self), chat_id, question, options) } /// Use this method when you need to tell the user that something is @@ -509,14 +541,14 @@ impl Bot { /// [ImageBot]: https://t.me/imagebot /// [`Bot::send_chat_action`]: crate::Bot::send_chat_action pub fn send_chat_action( - &self, + self: &Arc, chat_id: C, action: SendChatActionKind, ) -> SendChatAction where C: Into, { - SendChatAction::new(self, chat_id, action) + SendChatAction::new(Arc::clone(self), chat_id, action) } /// Use this method to get a list of profile pictures for a user. @@ -526,10 +558,10 @@ impl Bot { /// # Params /// - `user_id`: Unique identifier of the target user. pub fn get_user_profile_photos( - &self, + self: &Arc, user_id: i32, ) -> GetUserProfilePhotos { - GetUserProfilePhotos::new(self, user_id) + GetUserProfilePhotos::new(Arc::clone(self), user_id) } /// Use this method to get basic info about a file and prepare it for @@ -554,11 +586,11 @@ impl Bot { /// /// [`File`]: crate::types::file /// [`GetFile`]: self::GetFile - pub fn get_file(&self, file_id: F) -> GetFile + pub fn get_file(self: &Arc, file_id: F) -> GetFile where F: Into, { - GetFile::new(self, file_id) + GetFile::new(Arc::clone(self), file_id) } /// Use this method to kick a user from a group, a supergroup or a channel. @@ -577,14 +609,14 @@ impl Bot { /// /// [unbanned]: crate::Bot::unban_chat_member pub fn kick_chat_member( - &self, + self: &Arc, chat_id: C, user_id: i32, ) -> KickChatMember where C: Into, { - KickChatMember::new(self, chat_id, user_id) + KickChatMember::new(Arc::clone(self), chat_id, user_id) } /// Use this method to unban a previously kicked user in a supergroup or @@ -599,14 +631,14 @@ impl Bot { /// target supergroup or channel (in the format `@channelusername`). /// - `user_id`: Unique identifier of the target user. pub fn unban_chat_member( - &self, + self: &Arc, chat_id: C, user_id: i32, ) -> UnbanChatMember where C: Into, { - UnbanChatMember::new(self, chat_id, user_id) + UnbanChatMember::new(Arc::clone(self), chat_id, user_id) } /// Use this method to restrict a user in a supergroup. @@ -623,7 +655,7 @@ impl Bot { /// - `user_id`: Unique identifier of the target user. /// - `permissions`: New user permissions. pub fn restrict_chat_member( - &self, + self: &Arc, chat_id: C, user_id: i32, permissions: ChatPermissions, @@ -631,7 +663,7 @@ impl Bot { where C: Into, { - RestrictChatMember::new(self, chat_id, user_id, permissions) + RestrictChatMember::new(Arc::clone(self), chat_id, user_id, permissions) } /// Use this method to promote or demote a user in a supergroup or a @@ -648,14 +680,14 @@ impl Bot { /// target supergroup or channel (in the format `@channelusername`). /// - `user_id`: Unique identifier of the target user. pub fn promote_chat_member( - &self, + self: &Arc, chat_id: C, user_id: i32, ) -> PromoteChatMember where C: Into, { - PromoteChatMember::new(self, chat_id, user_id) + PromoteChatMember::new(Arc::clone(self), chat_id, user_id) } /// Use this method to set default chat permissions for all members. @@ -670,14 +702,14 @@ impl Bot { /// target supergroup or channel (in the format `@channelusername`). /// - `permissions`: New default chat permissions. pub fn set_chat_permissions( - &self, + self: &Arc, chat_id: C, permissions: ChatPermissions, ) -> SetChatPermissions where C: Into, { - SetChatPermissions::new(self, chat_id, permissions) + SetChatPermissions::new(Arc::clone(self), chat_id, permissions) } /// Use this method to generate a new invite link for a chat; any previously @@ -703,11 +735,14 @@ impl Bot { /// /// [`Bot::export_chat_invite_link`]: crate::Bot::export_chat_invite_link /// [`Bot::get_chat`]: crate::Bot::get_chat - pub fn export_chat_invite_link(&self, chat_id: C) -> ExportChatInviteLink + pub fn export_chat_invite_link( + self: &Arc, + chat_id: C, + ) -> ExportChatInviteLink where C: Into, { - ExportChatInviteLink::new(self, chat_id) + ExportChatInviteLink::new(Arc::clone(self), chat_id) } /// Use this method to set a new profile photo for the chat. @@ -723,14 +758,14 @@ impl Bot { /// target supergroup or channel (in the format `@channelusername`). /// - `photo`: New chat photo, uploaded using `multipart/form-data`. pub fn set_chat_photo( - &self, + self: &Arc, chat_id: C, photo: InputFile, ) -> SetChatPhoto where C: Into, { - SetChatPhoto::new(self, chat_id, photo) + SetChatPhoto::new(Arc::clone(self), chat_id, photo) } /// Use this method to delete a chat photo. Photos can't be changed for @@ -742,11 +777,11 @@ impl Bot { /// # Params /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). - pub fn delete_chat_photo(&self, chat_id: C) -> DeleteChatPhoto + pub fn delete_chat_photo(self: &Arc, chat_id: C) -> DeleteChatPhoto where C: Into, { - DeleteChatPhoto::new(self, chat_id) + DeleteChatPhoto::new(Arc::clone(self), chat_id) } /// Use this method to change the title of a chat. @@ -761,12 +796,16 @@ impl Bot { /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). /// - `title`: New chat title, 1-255 characters. - pub fn set_chat_title(&self, chat_id: C, title: T) -> SetChatTitle + pub fn set_chat_title( + self: &Arc, + chat_id: C, + title: T, + ) -> SetChatTitle where C: Into, T: Into, { - SetChatTitle::new(self, chat_id, title) + SetChatTitle::new(Arc::clone(self), chat_id, title) } /// Use this method to change the description of a group, a supergroup or a @@ -780,11 +819,14 @@ impl Bot { /// # Params /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). - pub fn set_chat_description(&self, chat_id: C) -> SetChatDescription + pub fn set_chat_description( + self: &Arc, + chat_id: C, + ) -> SetChatDescription where C: Into, { - SetChatDescription::new(self, chat_id) + SetChatDescription::new(Arc::clone(self), chat_id) } /// Use this method to pin a message in a group, a supergroup, or a channel. @@ -800,14 +842,14 @@ impl Bot { /// target supergroup or channel (in the format `@channelusername`). /// - `message_id`: Identifier of a message to pin. pub fn pin_chat_message( - &self, + self: &Arc, chat_id: C, message_id: i32, ) -> PinChatMessage where C: Into, { - PinChatMessage::new(self, chat_id, message_id) + PinChatMessage::new(Arc::clone(self), chat_id, message_id) } /// Use this method to unpin a message in a group, a supergroup, or a @@ -822,11 +864,14 @@ impl Bot { /// # Params /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). - pub fn unpin_chat_message(&self, chat_id: C) -> UnpinChatMessage + pub fn unpin_chat_message( + self: &Arc, + chat_id: C, + ) -> UnpinChatMessage where C: Into, { - UnpinChatMessage::new(self, chat_id) + UnpinChatMessage::new(Arc::clone(self), chat_id) } /// Use this method for your bot to leave a group, supergroup or channel. @@ -836,11 +881,11 @@ impl Bot { /// # Params /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). - pub fn leave_chat(&self, chat_id: C) -> LeaveChat + pub fn leave_chat(self: &Arc, chat_id: C) -> LeaveChat where C: Into, { - LeaveChat::new(self, chat_id) + LeaveChat::new(Arc::clone(self), chat_id) } /// Use this method to get up to date information about the chat (current @@ -852,11 +897,11 @@ impl Bot { /// # Params /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). - pub fn get_chat(&self, chat_id: C) -> GetChat + pub fn get_chat(self: &Arc, chat_id: C) -> GetChat where C: Into, { - GetChat::new(self, chat_id) + GetChat::new(Arc::clone(self), chat_id) } /// Use this method to get a list of administrators in a chat. @@ -870,13 +915,13 @@ impl Bot { /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). pub fn get_chat_administrators( - &self, + self: &Arc, chat_id: C, ) -> GetChatAdministrators where C: Into, { - GetChatAdministrators::new(self, chat_id) + GetChatAdministrators::new(Arc::clone(self), chat_id) } /// Use this method to get the number of members in a chat. @@ -886,11 +931,14 @@ impl Bot { /// # Params /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). - pub fn get_chat_members_count(&self, chat_id: C) -> GetChatMembersCount + pub fn get_chat_members_count( + self: &Arc, + chat_id: C, + ) -> GetChatMembersCount where C: Into, { - GetChatMembersCount::new(self, chat_id) + GetChatMembersCount::new(Arc::clone(self), chat_id) } /// Use this method to get information about a member of a chat. @@ -901,11 +949,15 @@ impl Bot { /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). /// - `user_id`: Unique identifier of the target user. - pub fn get_chat_member(&self, chat_id: C, user_id: i32) -> GetChatMember + pub fn get_chat_member( + self: &Arc, + chat_id: C, + user_id: i32, + ) -> GetChatMember where C: Into, { - GetChatMember::new(self, chat_id, user_id) + GetChatMember::new(Arc::clone(self), chat_id, user_id) } /// Use this method to set a new group sticker set for a supergroup. @@ -923,7 +975,7 @@ impl Bot { /// - `sticker_set_name`: Name of the sticker set to be set as the group /// sticker set. pub fn set_chat_sticker_set( - &self, + self: &Arc, chat_id: C, sticker_set_name: S, ) -> SetChatStickerSet @@ -931,7 +983,7 @@ impl Bot { C: Into, S: Into, { - SetChatStickerSet::new(self, chat_id, sticker_set_name) + SetChatStickerSet::new(Arc::clone(self), chat_id, sticker_set_name) } /// Use this method to delete a group sticker set from a supergroup. @@ -948,11 +1000,14 @@ impl Bot { /// target supergroup (in the format `@supergroupusername`). /// /// [`Bot::get_chat`]: crate::Bot::get_chat - pub fn delete_chat_sticker_set(&self, chat_id: C) -> DeleteChatStickerSet + pub fn delete_chat_sticker_set( + self: &Arc, + chat_id: C, + ) -> DeleteChatStickerSet where C: Into, { - DeleteChatStickerSet::new(self, chat_id) + DeleteChatStickerSet::new(Arc::clone(self), chat_id) } /// Use this method to send answers to callback queries sent from [inline @@ -968,13 +1023,13 @@ impl Bot { /// /// [inline keyboards]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating pub fn answer_callback_query( - &self, + self: &Arc, callback_query_id: C, ) -> AnswerCallbackQuery where C: Into, { - AnswerCallbackQuery::new(self, callback_query_id) + AnswerCallbackQuery::new(Arc::clone(self), callback_query_id) } /// Use this method to edit text and game messages. @@ -990,14 +1045,14 @@ impl Bot { /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True pub fn edit_message_text( - &self, + self: &Arc, chat_or_inline_message: ChatOrInlineMessage, text: T, ) -> EditMessageText where T: Into, { - EditMessageText::new(self, chat_or_inline_message, text) + EditMessageText::new(Arc::clone(self), chat_or_inline_message, text) } /// Use this method to edit captions of messages. @@ -1010,10 +1065,10 @@ impl Bot { /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True pub fn edit_message_caption( - &self, + self: &Arc, chat_or_inline_message: ChatOrInlineMessage, ) -> EditMessageCaption { - EditMessageCaption::new(self, chat_or_inline_message) + EditMessageCaption::new(Arc::clone(self), chat_or_inline_message) } /// Use this method to edit animation, audio, document, photo, or video @@ -1031,11 +1086,11 @@ impl Bot { /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True pub fn edit_message_media( - &self, + self: &Arc, chat_or_inline_message: ChatOrInlineMessage, media: InputMedia, ) -> EditMessageMedia { - EditMessageMedia::new(self, chat_or_inline_message, media) + EditMessageMedia::new(Arc::clone(self), chat_or_inline_message, media) } /// Use this method to edit only the reply markup of messages. @@ -1048,10 +1103,10 @@ impl Bot { /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True pub fn edit_message_reply_markup( - &self, + self: &Arc, chat_or_inline_message: ChatOrInlineMessage, ) -> EditMessageReplyMarkup { - EditMessageReplyMarkup::new(self, chat_or_inline_message) + EditMessageReplyMarkup::new(Arc::clone(self), chat_or_inline_message) } /// Use this method to stop a poll which was sent by the bot. @@ -1063,11 +1118,15 @@ impl Bot { /// - `chat_id`: Unique identifier for the target chat or username of the /// target channel (in the format `@channelusername`). /// - `message_id`: Identifier of the original message with the poll. - pub fn stop_poll(&self, chat_id: C, message_id: i32) -> StopPoll + pub fn stop_poll( + self: &Arc, + chat_id: C, + message_id: i32, + ) -> StopPoll where C: Into, { - StopPoll::new(self, chat_id, message_id) + StopPoll::new(Arc::clone(self), chat_id, message_id) } /// Use this method to delete a message, including service messages. @@ -1091,14 +1150,14 @@ impl Bot { /// target channel (in the format `@channelusername`). /// - `message_id`: Identifier of the message to delete. pub fn delete_message( - &self, + self: &Arc, chat_id: C, message_id: i32, ) -> DeleteMessage where C: Into, { - DeleteMessage::new(self, chat_id, message_id) + DeleteMessage::new(Arc::clone(self), chat_id, message_id) } /// Use this method to send static .WEBP or [animated] .TGS stickers. @@ -1120,11 +1179,15 @@ impl Bot { /// [`InputFile::Url`]: crate::types::InputFile::Url /// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn send_sticker(&self, chat_id: C, sticker: InputFile) -> SendSticker + pub fn send_sticker( + self: &Arc, + chat_id: C, + sticker: InputFile, + ) -> SendSticker where C: Into, { - SendSticker::new(self, chat_id, sticker) + SendSticker::new(Arc::clone(self), chat_id, sticker) } /// Use this method to get a sticker set. @@ -1133,11 +1196,11 @@ impl Bot { /// /// # Params /// - `name`: Name of the sticker set. - pub fn get_sticker_set(&self, name: N) -> GetStickerSet + pub fn get_sticker_set(self: &Arc, name: N) -> GetStickerSet where N: Into, { - GetStickerSet::new(self, name) + GetStickerSet::new(Arc::clone(self), name) } /// Use this method to upload a .png file with a sticker for later use in @@ -1157,11 +1220,11 @@ impl Bot { /// [`Bot::create_new_sticker_set`]: crate::Bot::create_new_sticker_set /// [`Bot::add_sticker_to_set`]: crate::Bot::add_sticker_to_set pub fn upload_sticker_file( - &self, + self: &Arc, user_id: i32, png_sticker: InputFile, ) -> UploadStickerFile { - UploadStickerFile::new(self, user_id, png_sticker) + UploadStickerFile::new(Arc::clone(self), user_id, png_sticker) } /// Use this method to create new sticker set owned by a user. The bot will @@ -1193,7 +1256,7 @@ impl Bot { /// [`InputFile::Url`]: crate::types::InputFile::Url /// [`InputFile::FileId`]: crate::types::InputFile::FileId pub fn create_new_sticker_set( - &self, + self: &Arc, user_id: i32, name: N, title: T, @@ -1206,7 +1269,7 @@ impl Bot { E: Into, { CreateNewStickerSet::new( - self, + Arc::clone(self), user_id, name, title, @@ -1236,7 +1299,7 @@ impl Bot { /// [`InputFile::Url`]: crate::types::InputFile::Url /// [`InputFile::FileId`]: crate::types::InputFile::FileId pub fn add_sticker_to_set( - &self, + self: &Arc, user_id: i32, name: N, png_sticker: InputFile, @@ -1246,7 +1309,13 @@ impl Bot { N: Into, E: Into, { - AddStickerToSet::new(self, user_id, name, png_sticker, emojis) + AddStickerToSet::new( + Arc::clone(self), + user_id, + name, + png_sticker, + emojis, + ) } /// Use this method to move a sticker in a set created by the bot to a @@ -1258,14 +1327,14 @@ impl Bot { /// - `sticker`: File identifier of the sticker. /// - `position`: New sticker position in the set, zero-based. pub fn set_sticker_position_in_set( - &self, + self: &Arc, sticker: S, position: i32, ) -> SetStickerPositionInSet where S: Into, { - SetStickerPositionInSet::new(self, sticker, position) + SetStickerPositionInSet::new(Arc::clone(self), sticker, position) } /// Use this method to delete a sticker from a set created by the bot. @@ -1274,11 +1343,14 @@ impl Bot { /// /// # Params /// - `sticker`: File identifier of the sticker. - pub fn delete_sticker_from_set(&self, sticker: S) -> DeleteStickerFromSet + pub fn delete_sticker_from_set( + self: &Arc, + sticker: S, + ) -> DeleteStickerFromSet where S: Into, { - DeleteStickerFromSet::new(self, sticker) + DeleteStickerFromSet::new(Arc::clone(self), sticker) } /// Use this method to send answers to an inline query. @@ -1291,7 +1363,7 @@ impl Bot { /// - `inline_query_id`: Unique identifier for the answered query. /// - `results`: A JSON-serialized array of results for the inline query. pub fn answer_inline_query( - &self, + self: &Arc, inline_query_id: I, results: R, ) -> AnswerInlineQuery @@ -1299,7 +1371,7 @@ impl Bot { I: Into, R: Into>, { - AnswerInlineQuery::new(self, inline_query_id, results) + AnswerInlineQuery::new(Arc::clone(self), inline_query_id, results) } /// Use this method to send invoices. @@ -1325,7 +1397,7 @@ impl Bot { /// [@Botfather]: https://t.me/botfather #[allow(clippy::too_many_arguments)] pub fn send_invoice( - &self, + self: &Arc, chat_id: i32, title: T, description: D, @@ -1345,7 +1417,7 @@ impl Bot { Pr: Into>, { SendInvoice::new( - self, + Arc::clone(self), chat_id, title, description, @@ -1373,14 +1445,14 @@ impl Bot { /// /// [`Update`]: crate::types::Update pub fn answer_shipping_query( - &self, + self: &Arc, shipping_query_id: S, ok: bool, ) -> AnswerShippingQuery where S: Into, { - AnswerShippingQuery::new(self, shipping_query_id, ok) + AnswerShippingQuery::new(Arc::clone(self), shipping_query_id, ok) } /// Once the user has confirmed their payment and shipping details, the Bot @@ -1397,16 +1469,17 @@ impl Bot { /// - `ok`: Specify `true` if everything is alright (goods are available, /// etc.) and the bot is ready to proceed with the order. Use False if /// there are any problems. + /// /// [`Update`]: crate::types::Update pub fn answer_pre_checkout_query

( - &self, + self: &Arc, pre_checkout_query_id: P, ok: bool, ) -> AnswerPreCheckoutQuery where P: Into, { - AnswerPreCheckoutQuery::new(self, pre_checkout_query_id, ok) + AnswerPreCheckoutQuery::new(Arc::clone(self), pre_checkout_query_id, ok) } /// Use this method to send a game. @@ -1419,11 +1492,15 @@ impl Bot { /// identifier for the game. Set up your games via [@Botfather]. /// /// [@Botfather]: https://t.me/botfather - pub fn send_game(&self, chat_id: i32, game_short_name: G) -> SendGame + pub fn send_game( + self: &Arc, + chat_id: i32, + game_short_name: G, + ) -> SendGame where G: Into, { - SendGame::new(self, chat_id, game_short_name) + SendGame::new(Arc::clone(self), chat_id, game_short_name) } /// Use this method to set the score of the specified user in a game. @@ -1442,12 +1519,17 @@ impl Bot { /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True pub fn set_game_score( - &self, + self: &Arc, chat_or_inline_message: ChatOrInlineMessage, user_id: i32, score: i32, ) -> SetGameScore { - SetGameScore::new(self, chat_or_inline_message, user_id, score) + SetGameScore::new( + Arc::clone(self), + chat_or_inline_message, + user_id, + score, + ) } /// Use this method to get data for high score tables. @@ -1466,11 +1548,15 @@ impl Bot { /// # Params /// - `user_id`: Target user id. pub fn get_game_high_scores( - &self, + self: &Arc, chat_or_inline_message: ChatOrInlineMessage, user_id: i32, ) -> GetGameHighScores { - GetGameHighScores::new(self, chat_or_inline_message, user_id) + GetGameHighScores::new( + Arc::clone(self), + chat_or_inline_message, + user_id, + ) } /// Use this method to set a custom title for an administrator in a @@ -1485,7 +1571,7 @@ impl Bot { /// - `custom_title`: New custom title for the administrator; 0-16 /// characters, emoji are not allowed. pub fn set_chat_administrator_custom_title( - &self, + self: &Arc, chat_id: C, user_id: i32, custom_title: CT, @@ -1495,7 +1581,7 @@ impl Bot { CT: Into, { SetChatAdministratorCustomTitle::new( - self, + Arc::clone(self), chat_id, user_id, custom_title, diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 5e26de9e..942caae1 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -1,4 +1,5 @@ use reqwest::Client; +use std::sync::Arc; mod api; mod download; @@ -11,24 +12,58 @@ pub struct Bot { } impl Bot { - pub fn new(token: S) -> Self - where - S: Into, - { - Bot { - token: token.into(), - client: Client::new(), - } + /// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a + /// bot's token) and the default [`reqwest::Client`]. + /// + /// # Panics + /// If cannot get the `TELOXIDE_TOKEN` environmental variable. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + pub fn from_env() -> Arc { + Self::new( + std::env::var("TELOXIDE_TOKEN") + .expect("Cannot get the TELOXIDE_TOKEN env variable"), + ) } - pub fn with_client(token: S, client: Client) -> Self + /// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a + /// bot's token) and your [`reqwest::Client`]. + /// + /// # Panics + /// If cannot get the `TELOXIDE_TOKEN` environmental variable. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + pub fn from_env_with_client(client: Client) -> Arc { + Self::with_client( + std::env::var("TELOXIDE_TOKEN") + .expect("Cannot get the TELOXIDE_TOKEN env variable"), + client, + ) + } + + /// Creates a new `Bot` with the specified token and the default + /// [`reqwest::Client`]. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + pub fn new(token: S) -> Arc where S: Into, { - Bot { + Self::with_client(token, Client::new()) + } + + /// Creates a new `Bot` with the specified token and your + /// [`reqwest::Client`]. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + pub fn with_client(token: S, client: Client) -> Arc + where + S: Into, + { + Arc::new(Self { token: token.into(), client, - } + }) } } diff --git a/src/dispatching/chat/chat_update.rs b/src/dispatching/chat/chat_update.rs deleted file mode 100644 index 32242b49..00000000 --- a/src/dispatching/chat/chat_update.rs +++ /dev/null @@ -1,18 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{CallbackQuery, Message}; - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct ChatUpdate { - pub id: i32, - - pub kind: ChatUpdateKind, -} - -#[allow(clippy::large_enum_variant)] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum ChatUpdateKind { - Message(Message), - EditedMessage(Message), - CallbackQuery(CallbackQuery), -} diff --git a/src/dispatching/chat/dispatcher.rs b/src/dispatching/chat/dispatcher.rs deleted file mode 100644 index 14505ed9..00000000 --- a/src/dispatching/chat/dispatcher.rs +++ /dev/null @@ -1,106 +0,0 @@ -use super::{ - super::DispatchResult, - storage::{InMemStorage, Storage}, -}; -use crate::{ - dispatching::{ - chat::{ChatUpdate, ChatUpdateKind}, - Handler, SessionState, - }, - types::{Update, UpdateKind}, -}; - -/// A dispatcher that dispatches updates from chats. -pub struct Dispatcher<'a, Session, H> { - storage: Box + 'a>, - handler: H, -} - -impl<'a, Session, H> Dispatcher<'a, Session, H> -where - Session: Default + 'a, - H: Handler, -{ - /// Creates a dispatcher with the specified `handler` and [`InMemStorage`] - /// (a default storage). - /// - /// [`InMemStorage`]: crate::dispatching::private::InMemStorage - pub fn new(handler: H) -> Self { - Self { - storage: Box::new(InMemStorage::default()), - handler, - } - } - - /// Creates a dispatcher with the specified `handler` and `storage`. - pub fn with_storage(handler: H, storage: Stg) -> Self - where - Stg: Storage + 'a, - { - Self { - storage: Box::new(storage), - handler, - } - } - - /// Dispatches a single `update`. - /// - /// ## Returns - /// Returns [`DispatchResult::Handled`] if `update` was supplied to a - /// handler, and [`DispatchResult::Unhandled`] if it was an update not - /// from a chat. - /// - /// [`DispatchResult::Handled`]: crate::dispatching::DispatchResult::Handled - /// [`DispatchResult::Unhandled`]: - /// crate::dispatching::DispatchResult::Unhandled - pub async fn dispatch(&mut self, update: Update) -> DispatchResult { - let chat_update = match update.kind { - UpdateKind::Message(msg) => ChatUpdate { - id: update.id, - kind: ChatUpdateKind::Message(msg), - }, - UpdateKind::EditedMessage(msg) => ChatUpdate { - id: update.id, - kind: ChatUpdateKind::EditedMessage(msg), - }, - UpdateKind::CallbackQuery(query) => ChatUpdate { - id: update.id, - kind: ChatUpdateKind::CallbackQuery(query), - }, - _ => return DispatchResult::Unhandled, - }; - - let chat_id = match &chat_update.kind { - ChatUpdateKind::Message(msg) => msg.chat.id, - ChatUpdateKind::EditedMessage(msg) => msg.chat.id, - ChatUpdateKind::CallbackQuery(query) => match &query.message { - None => return DispatchResult::Unhandled, - Some(msg) => msg.chat.id, - }, - }; - - let session = self - .storage - .remove_session(chat_id) - .await - .unwrap_or_default(); - - if let SessionState::Continue(session) = - self.handler.handle(session, chat_update).await - { - if self - .storage - .update_session(chat_id, session) - .await - .is_some() - { - panic!( - "We previously storage.remove_session() so \ - storage.update_session() must return None" - ); - } - } - - DispatchResult::Handled - } -} diff --git a/src/dispatching/chat/mod.rs b/src/dispatching/chat/mod.rs deleted file mode 100644 index 7accabc6..00000000 --- a/src/dispatching/chat/mod.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! Dispatching updates from chats. -//! -//! There are four main components: -//! -//! 1. Your session type `Session`, which designates a dialogue state at the -//! current moment. -//! 2. [`Storage`] that encapsulates all the sessions. -//! 3. Your handler of type `H: async Fn(Session, Update) -> -//! HandleResult` that receives an update and turns your session into -//! the next state. -//! 4. [`Dispatcher`], which encapsulates your handler and [`Storage`], and has -//! the [`dispatch(Update) -> DispatchResult`] function. -//! -//! Every time you call [`.dispatch(update)`] on your dispatcher, the following -//! steps are executed: -//! -//! 1. If a supplied update is not from a chat, return -//! [`DispatchResult::Unhandled`]. -//! 2. If a storage doesn't contain a session from this chat, supply -//! `Session::default()` into you handler, otherwise, supply the previous -//! session. -//! 3. If a handler has returned [`SessionState::Terminate`], remove the -//! session from a storage, otherwise force the storage to update the session. -//! -//! [`Storage`]: crate::dispatching::private::Storage -//! [`Dispatcher`]: crate::dispatching::private::Dispatcher -//! [`dispatch(Update) -> DispatchResult`]: -//! crate::dispatching::private::Dispatcher::dispatch -//! [`.dispatch(update)`]: crate::dispatching::private::Dispatcher::dispatch -//! [`DispatchResult::Unhandled`]: crate::dispatching::DispatchResult::Unhandled -//! [`SessionState::Terminate`]: crate::dispatching::SessionState::Terminate - -// TODO: examples - -mod chat_update; -mod dispatcher; -mod storage; - -pub use chat_update::*; -pub use dispatcher::*; -pub use storage::*; diff --git a/src/dispatching/chat/storage/in_mem_storage.rs b/src/dispatching/chat/storage/in_mem_storage.rs deleted file mode 100644 index 140ee133..00000000 --- a/src/dispatching/chat/storage/in_mem_storage.rs +++ /dev/null @@ -1,32 +0,0 @@ -use async_trait::async_trait; - -use super::Storage; -use std::collections::HashMap; - -/// A memory storage based on a hash map. Stores all the sessions directly in -/// RAM. -/// -/// ## Note -/// All the sessions will be lost after you restart your bot. If you need to -/// store them somewhere on a drive, you need to implement a storage -/// communicating with a DB. -#[derive(Clone, Debug, Eq, PartialEq, Default)] -pub struct InMemStorage { - map: HashMap, -} - -#[async_trait(?Send)] -#[async_trait] -impl Storage for InMemStorage { - async fn remove_session(&mut self, chat_id: i64) -> Option { - self.map.remove(&chat_id) - } - - async fn update_session( - &mut self, - chat_id: i64, - state: Session, - ) -> Option { - self.map.insert(chat_id, state) - } -} diff --git a/src/dispatching/chat/storage/mod.rs b/src/dispatching/chat/storage/mod.rs deleted file mode 100644 index e3e12bbf..00000000 --- a/src/dispatching/chat/storage/mod.rs +++ /dev/null @@ -1,32 +0,0 @@ -mod in_mem_storage; - -use async_trait::async_trait; -pub use in_mem_storage::InMemStorage; - -/// A storage of sessions. -/// -/// You can implement this trait for a structure that communicates with a DB and -/// be sure that after you restart your bot, all the sessions won't be lost. -/// -/// For a storage based on a simple hash map, see [`InMemStorage`]. -/// -/// [`InMemStorage`]: crate::dispatching::private::InMemStorage -#[async_trait(?Send)] -#[async_trait] -pub trait Storage { - /// Removes a session with the specified `chat_id`. - /// - /// Returns `None` if there wasn't such a session, `Some(session)` if a - /// `session` was deleted. - async fn remove_session(&mut self, chat_id: i64) -> Option; - - /// Updates a session with the specified `chat_id`. - /// - /// Returns `None` if there wasn't such a session, `Some(session)` if a - /// `session` was updated. - async fn update_session( - &mut self, - chat_id: i64, - session: Session, - ) -> Option; -} diff --git a/src/dispatching/ctx_handlers.rs b/src/dispatching/ctx_handlers.rs new file mode 100644 index 00000000..ba6e22bc --- /dev/null +++ b/src/dispatching/ctx_handlers.rs @@ -0,0 +1,31 @@ +use std::{future::Future, pin::Pin}; + +/// An asynchronous handler of a context. +/// +/// See [the module-level documentation for the design +/// overview](crate::dispatching). +pub trait CtxHandler { + #[must_use] + fn handle_ctx<'a>( + &'a self, + ctx: Ctx, + ) -> Pin + 'a>> + where + Ctx: 'a; +} + +impl CtxHandler for F +where + F: Fn(Ctx) -> Fut, + Fut: Future, +{ + fn handle_ctx<'a>( + &'a self, + ctx: Ctx, + ) -> Pin + 'a>> + where + Ctx: 'a, + { + Box::pin(async move { self(ctx).await }) + } +} diff --git a/src/dispatching/dialogue/dialogue_dispatcher.rs b/src/dispatching/dialogue/dialogue_dispatcher.rs new file mode 100644 index 00000000..4b5fea97 --- /dev/null +++ b/src/dispatching/dialogue/dialogue_dispatcher.rs @@ -0,0 +1,97 @@ +use crate::dispatching::{ + dialogue::{ + DialogueHandlerCtx, DialogueStage, GetChatId, InMemStorage, Storage, + }, + CtxHandler, DispatcherHandlerCtx, +}; +use std::{future::Future, pin::Pin}; + +/// A dispatcher of dialogues. +/// +/// Note that `DialogueDispatcher` implements `CtxHandler`, so you can just put +/// an instance of this dispatcher into the [`Dispatcher`]'s methods. +/// +/// [`Dispatcher`]: crate::dispatching::Dispatcher +pub struct DialogueDispatcher<'a, D, H> { + storage: Box + 'a>, + handler: H, +} + +impl<'a, D, H> DialogueDispatcher<'a, D, H> +where + D: Default + 'a, +{ + /// Creates a dispatcher with the specified `handler` and [`InMemStorage`] + /// (a default storage). + /// + /// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage + #[must_use] + pub fn new(handler: H) -> Self { + Self { + storage: Box::new(InMemStorage::default()), + handler, + } + } + + /// Creates a dispatcher with the specified `handler` and `storage`. + #[must_use] + pub fn with_storage(handler: H, storage: Stg) -> Self + where + Stg: Storage + 'a, + { + Self { + storage: Box::new(storage), + handler, + } + } +} + +impl<'a, D, H, Upd> CtxHandler, Result<(), ()>> + for DialogueDispatcher<'a, D, H> +where + H: CtxHandler, DialogueStage>, + Upd: GetChatId, + D: Default, +{ + fn handle_ctx<'b>( + &'b self, + ctx: DispatcherHandlerCtx, + ) -> Pin> + 'b>> + where + Upd: 'b, + { + Box::pin(async move { + let chat_id = ctx.update.chat_id(); + + let dialogue = self + .storage + .remove_dialogue(chat_id) + .await + .unwrap_or_default(); + + if let DialogueStage::Next(new_dialogue) = self + .handler + .handle_ctx(DialogueHandlerCtx { + bot: ctx.bot, + update: ctx.update, + dialogue, + }) + .await + { + if self + .storage + .update_dialogue(chat_id, new_dialogue) + .await + .is_some() + { + panic!( + "We previously storage.remove_dialogue() so \ + storage.update_dialogue() must return None" + ); + } + } + + Ok(()) + }) + } +} diff --git a/src/dispatching/dialogue/dialogue_handler_ctx.rs b/src/dispatching/dialogue/dialogue_handler_ctx.rs new file mode 100644 index 00000000..257744aa --- /dev/null +++ b/src/dispatching/dialogue/dialogue_handler_ctx.rs @@ -0,0 +1,190 @@ +use crate::{ + dispatching::dialogue::GetChatId, + requests::{ + DeleteMessage, EditMessageCaption, EditMessageText, ForwardMessage, + PinChatMessage, SendAnimation, SendAudio, SendContact, SendDocument, + SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendSticker, + SendVenue, SendVideo, SendVideoNote, SendVoice, + }, + types::{ChatId, ChatOrInlineMessage, InputFile, InputMedia, Message}, + Bot, +}; +use std::sync::Arc; + +/// A context of a [`DialogueDispatcher`]'s message handler. +/// +/// [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher +pub struct DialogueHandlerCtx { + pub bot: Arc, + pub update: Upd, + pub dialogue: D, +} + +impl DialogueHandlerCtx { + /// Creates a new instance with the provided fields. + pub fn new(bot: Arc, update: Upd, dialogue: D) -> Self { + Self { + bot, + update, + dialogue, + } + } + + /// Creates a new instance by substituting a dialogue and preserving + /// `self.bot` and `self.update`. + pub fn with_new_dialogue( + self, + new_dialogue: Nd, + ) -> DialogueHandlerCtx { + DialogueHandlerCtx { + bot: self.bot, + update: self.update, + dialogue: new_dialogue, + } + } +} + +impl GetChatId for DialogueHandlerCtx +where + Upd: GetChatId, +{ + fn chat_id(&self) -> i64 { + self.update.chat_id() + } +} + +impl DialogueHandlerCtx { + pub fn answer(&self, text: T) -> SendMessage + where + T: Into, + { + self.bot.send_message(self.chat_id(), text) + } + + pub fn reply_to(&self, text: T) -> SendMessage + where + T: Into, + { + self.bot + .send_message(self.chat_id(), text) + .reply_to_message_id(self.update.id) + } + + pub fn answer_photo(&self, photo: InputFile) -> SendPhoto { + self.bot.send_photo(self.update.chat.id, photo) + } + + pub fn answer_audio(&self, audio: InputFile) -> SendAudio { + self.bot.send_audio(self.update.chat.id, audio) + } + + pub fn answer_animation(&self, animation: InputFile) -> SendAnimation { + self.bot.send_animation(self.update.chat.id, animation) + } + + pub fn answer_document(&self, document: InputFile) -> SendDocument { + self.bot.send_document(self.update.chat.id, document) + } + + pub fn answer_video(&self, video: InputFile) -> SendVideo { + self.bot.send_video(self.update.chat.id, video) + } + + pub fn answer_voice(&self, voice: InputFile) -> SendVoice { + self.bot.send_voice(self.update.chat.id, voice) + } + + pub fn answer_media_group(&self, media_group: T) -> SendMediaGroup + where + T: Into>, + { + self.bot.send_media_group(self.update.chat.id, media_group) + } + + pub fn answer_location( + &self, + latitude: f32, + longitude: f32, + ) -> SendLocation { + self.bot + .send_location(self.update.chat.id, latitude, longitude) + } + + pub fn answer_venue( + &self, + latitude: f32, + longitude: f32, + title: T, + address: U, + ) -> SendVenue + where + T: Into, + U: Into, + { + self.bot.send_venue( + self.update.chat.id, + latitude, + longitude, + title, + address, + ) + } + + pub fn answer_video_note(&self, video_note: InputFile) -> SendVideoNote { + self.bot.send_video_note(self.update.chat.id, video_note) + } + + pub fn answer_contact( + &self, + phone_number: T, + first_name: U, + ) -> SendContact + where + T: Into, + U: Into, + { + self.bot + .send_contact(self.chat_id(), phone_number, first_name) + } + + pub fn answer_sticker(&self, sticker: InputFile) -> SendSticker { + self.bot.send_sticker(self.update.chat.id, sticker) + } + + pub fn forward_to(&self, chat_id: T) -> ForwardMessage + where + T: Into, + { + self.bot + .forward_message(chat_id, self.update.chat.id, self.update.id) + } + + pub fn edit_message_text(&self, text: T) -> EditMessageText + where + T: Into, + { + self.bot.edit_message_text( + ChatOrInlineMessage::Chat { + chat_id: self.update.chat.id.into(), + message_id: self.update.id, + }, + text, + ) + } + + pub fn edit_message_caption(&self) -> EditMessageCaption { + self.bot.edit_message_caption(ChatOrInlineMessage::Chat { + chat_id: self.update.chat.id.into(), + message_id: self.update.id, + }) + } + + pub fn delete_message(&self) -> DeleteMessage { + self.bot.delete_message(self.update.chat.id, self.update.id) + } + + pub fn pin_message(&self) -> PinChatMessage { + self.bot + .pin_chat_message(self.update.chat.id, self.update.id) + } +} diff --git a/src/dispatching/dialogue/dialogue_stage.rs b/src/dispatching/dialogue/dialogue_stage.rs new file mode 100644 index 00000000..afb5a31c --- /dev/null +++ b/src/dispatching/dialogue/dialogue_stage.rs @@ -0,0 +1,16 @@ +/// Continue or terminate a dialogue. +#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] +pub enum DialogueStage { + Next(D), + Exit, +} + +/// A shortcut for `Ok(DialogueStage::Next(dialogue))`. +pub fn next(dialogue: D) -> Result, E> { + Ok(DialogueStage::Next(dialogue)) +} + +/// A shortcut for `Ok(DialogueStage::Exit)`. +pub fn exit() -> Result, E> { + Ok(DialogueStage::Exit) +} diff --git a/src/dispatching/dialogue/get_chat_id.rs b/src/dispatching/dialogue/get_chat_id.rs new file mode 100644 index 00000000..d7e64206 --- /dev/null +++ b/src/dispatching/dialogue/get_chat_id.rs @@ -0,0 +1,13 @@ +use crate::types::Message; + +/// Something that has a chat ID. +pub trait GetChatId { + #[must_use] + fn chat_id(&self) -> i64; +} + +impl GetChatId for Message { + fn chat_id(&self) -> i64 { + self.chat.id + } +} diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs new file mode 100644 index 00000000..33fd28b6 --- /dev/null +++ b/src/dispatching/dialogue/mod.rs @@ -0,0 +1,48 @@ +//! Dealing with dialogues. +//! +//! There are four main components: +//! +//! 1. Your type `D`, which designates a dialogue state at the current +//! moment. +//! 2. [`Storage`], which encapsulates all the dialogues. +//! 3. Your handler, which receives an update and turns your dialogue into the +//! next state. +//! 4. [`DialogueDispatcher`], which encapsulates your handler, [`Storage`], +//! and implements [`CtxHandler`]. +//! +//! You supply [`DialogueDispatcher`] into [`Dispatcher`]. Every time +//! [`Dispatcher`] calls `DialogueDispatcher::handle_ctx(...)`, the following +//! steps are executed: +//! +//! 1. If a storage doesn't contain a dialogue from this chat, supply +//! `D::default()` into you handler, otherwise, supply the saved session +//! from this chat. +//! 2. If a handler has returned [`DialogueStage::Exit`], remove the session +//! from the storage, otherwise ([`DialogueStage::Next`]) force the storage to +//! update the session. +//! +//! Please, see [examples/dialogue_bot] as an example. +//! +//! [`Storage`]: crate::dispatching::dialogue::Storage +//! [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher +//! [`DialogueStage::Exit`]: +//! crate::dispatching::dialogue::DialogueStage::Exit +//! [`DialogueStage::Next`]: crate::dispatching::dialogue::DialogueStage::Next +//! [`CtxHandler`]: crate::dispatching::CtxHandler +//! [`Dispatcher`]: crate::dispatching::Dispatcher +//! [examples/dialogue_bot]: https://github.com/teloxide/teloxide/tree/dev/examples/dialogue_bot + +#![allow(clippy::module_inception)] +#![allow(clippy::type_complexity)] + +mod dialogue_dispatcher; +mod dialogue_handler_ctx; +mod dialogue_stage; +mod get_chat_id; +mod storage; + +pub use dialogue_dispatcher::DialogueDispatcher; +pub use dialogue_handler_ctx::DialogueHandlerCtx; +pub use dialogue_stage::{exit, next, DialogueStage}; +pub use get_chat_id::GetChatId; +pub use storage::{InMemStorage, Storage}; diff --git a/src/dispatching/dialogue/storage/in_mem_storage.rs b/src/dispatching/dialogue/storage/in_mem_storage.rs new file mode 100644 index 00000000..bfc1d033 --- /dev/null +++ b/src/dispatching/dialogue/storage/in_mem_storage.rs @@ -0,0 +1,29 @@ +use async_trait::async_trait; + +use super::Storage; +use std::collections::HashMap; +use tokio::sync::Mutex; + +/// A memory storage based on a hash map. Stores all the dialogues directly in +/// RAM. +/// +/// ## Note +/// All the dialogues will be lost after you restart your bot. If you need to +/// store them somewhere on a drive, you need to implement a storage +/// communicating with a DB. +#[derive(Debug, Default)] +pub struct InMemStorage { + map: Mutex>, +} + +#[async_trait(?Send)] +#[async_trait] +impl Storage for InMemStorage { + async fn remove_dialogue(&self, chat_id: i64) -> Option { + self.map.lock().await.remove(&chat_id) + } + + async fn update_dialogue(&self, chat_id: i64, dialogue: D) -> Option { + self.map.lock().await.insert(chat_id, dialogue) + } +} diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs new file mode 100644 index 00000000..f06fbf49 --- /dev/null +++ b/src/dispatching/dialogue/storage/mod.rs @@ -0,0 +1,28 @@ +mod in_mem_storage; + +use async_trait::async_trait; +pub use in_mem_storage::InMemStorage; + +/// A storage of dialogues. +/// +/// You can implement this trait for a structure that communicates with a DB and +/// be sure that after you restart your bot, all the dialogues won't be lost. +/// +/// For a storage based on a simple hash map, see [`InMemStorage`]. +/// +/// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage +#[async_trait(?Send)] +#[async_trait] +pub trait Storage { + /// Removes a dialogue with the specified `chat_id`. + /// + /// Returns `None` if there wasn't such a dialogue, `Some(dialogue)` if a + /// `dialogue` was deleted. + async fn remove_dialogue(&self, chat_id: i64) -> Option; + + /// Updates a dialogue with the specified `chat_id`. + /// + /// Returns `None` if there wasn't such a dialogue, `Some(dialogue)` if a + /// `dialogue` was updated. + async fn update_dialogue(&self, chat_id: i64, dialogue: D) -> Option; +} diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs new file mode 100644 index 00000000..82f16b2d --- /dev/null +++ b/src/dispatching/dispatcher.rs @@ -0,0 +1,381 @@ +use crate::{ + dispatching::{ + error_handlers::ErrorHandler, update_listeners, + update_listeners::UpdateListener, CtxHandler, DispatcherHandlerCtx, + DispatcherHandlerResult, LoggingErrorHandler, + }, + types::{ + CallbackQuery, ChosenInlineResult, InlineQuery, Message, Poll, + PollAnswer, PreCheckoutQuery, ShippingQuery, Update, UpdateKind, + }, + Bot, RequestError, +}; +use futures::{stream, StreamExt}; +use std::{fmt::Debug, future::Future, sync::Arc}; + +type Handlers<'a, Upd, HandlerE> = Vec< + Box< + dyn CtxHandler< + DispatcherHandlerCtx, + DispatcherHandlerResult, + > + 'a, + >, +>; + +/// One dispatcher to rule them all. +/// +/// See [the module-level documentation for the design +/// overview](crate::dispatching). +// HandlerE=RequestError doesn't work now, because of very poor type inference. +// See https://github.com/rust-lang/rust/issues/27336 for more details. +pub struct Dispatcher<'a, HandlerE = RequestError> { + bot: Arc, + + handlers_error_handler: Box + 'a>, + + update_handlers: Handlers<'a, Update, HandlerE>, + message_handlers: Handlers<'a, Message, HandlerE>, + edited_message_handlers: Handlers<'a, Message, HandlerE>, + channel_post_handlers: Handlers<'a, Message, HandlerE>, + edited_channel_post_handlers: Handlers<'a, Message, HandlerE>, + inline_query_handlers: Handlers<'a, InlineQuery, HandlerE>, + chosen_inline_result_handlers: Handlers<'a, ChosenInlineResult, HandlerE>, + callback_query_handlers: Handlers<'a, CallbackQuery, HandlerE>, + shipping_query_handlers: Handlers<'a, ShippingQuery, HandlerE>, + pre_checkout_query_handlers: Handlers<'a, PreCheckoutQuery, HandlerE>, + poll_handlers: Handlers<'a, Poll, HandlerE>, + poll_answer_handlers: Handlers<'a, PollAnswer, HandlerE>, +} + +impl<'a, HandlerE> Dispatcher<'a, HandlerE> +where + HandlerE: Debug + 'a, +{ + /// Constructs a new dispatcher with this `bot`. + #[must_use] + pub fn new(bot: Arc) -> Self { + Self { + bot, + handlers_error_handler: Box::new(LoggingErrorHandler::new( + "An error from a Dispatcher's handler", + )), + update_handlers: Vec::new(), + message_handlers: Vec::new(), + edited_message_handlers: Vec::new(), + channel_post_handlers: Vec::new(), + edited_channel_post_handlers: Vec::new(), + inline_query_handlers: Vec::new(), + chosen_inline_result_handlers: Vec::new(), + callback_query_handlers: Vec::new(), + shipping_query_handlers: Vec::new(), + pre_checkout_query_handlers: Vec::new(), + poll_handlers: Vec::new(), + poll_answer_handlers: Vec::new(), + } + } + + /// Registers a handler of errors, produced by other handlers. + #[must_use] + pub fn handlers_error_handler(mut self, val: T) -> Self + where + T: ErrorHandler + 'a, + { + self.handlers_error_handler = Box::new(val); + self + } + + #[must_use] + pub fn update_handler(mut self, h: &'a H) -> Self + where + H: CtxHandler, I> + 'a, + I: Into> + 'a, + { + self.update_handlers = register_handler(self.update_handlers, h); + self + } + + #[must_use] + pub fn message_handler(mut self, h: &'a H) -> Self + where + H: CtxHandler, I> + 'a, + I: Into> + 'a, + { + self.message_handlers = register_handler(self.message_handlers, h); + self + } + + #[must_use] + pub fn edited_message_handler(mut self, h: &'a H) -> Self + where + H: CtxHandler, I> + 'a, + I: Into> + 'a, + { + self.edited_message_handlers = + register_handler(self.edited_message_handlers, h); + self + } + + #[must_use] + pub fn channel_post_handler(mut self, h: &'a H) -> Self + where + H: CtxHandler, I> + 'a, + I: Into> + 'a, + { + self.channel_post_handlers = + register_handler(self.channel_post_handlers, h); + self + } + + #[must_use] + pub fn edited_channel_post_handler(mut self, h: &'a H) -> Self + where + H: CtxHandler, I> + 'a, + I: Into> + 'a, + { + self.edited_channel_post_handlers = + register_handler(self.edited_channel_post_handlers, h); + self + } + + #[must_use] + pub fn inline_query_handler(mut self, h: &'a H) -> Self + where + H: CtxHandler, I> + 'a, + I: Into> + 'a, + { + self.inline_query_handlers = + register_handler(self.inline_query_handlers, h); + self + } + + #[must_use] + pub fn chosen_inline_result_handler(mut self, h: &'a H) -> Self + where + H: CtxHandler, I> + 'a, + I: Into> + 'a, + { + self.chosen_inline_result_handlers = + register_handler(self.chosen_inline_result_handlers, h); + self + } + + #[must_use] + pub fn callback_query_handler(mut self, h: &'a H) -> Self + where + H: CtxHandler, I> + 'a, + I: Into> + 'a, + { + self.callback_query_handlers = + register_handler(self.callback_query_handlers, h); + self + } + + #[must_use] + pub fn shipping_query_handler(mut self, h: &'a H) -> Self + where + H: CtxHandler, I> + 'a, + I: Into> + 'a, + { + self.shipping_query_handlers = + register_handler(self.shipping_query_handlers, h); + self + } + + #[must_use] + pub fn pre_checkout_query_handler(mut self, h: &'a H) -> Self + where + H: CtxHandler, I> + 'a, + I: Into> + 'a, + { + self.pre_checkout_query_handlers = + register_handler(self.pre_checkout_query_handlers, h); + self + } + + #[must_use] + pub fn poll_handler(mut self, h: &'a H) -> Self + where + H: CtxHandler, I> + 'a, + I: Into> + 'a, + { + self.poll_handlers = register_handler(self.poll_handlers, h); + self + } + + #[must_use] + pub fn poll_answer_handler(mut self, h: &'a H) -> Self + where + H: CtxHandler, I> + 'a, + I: Into> + 'a, + { + self.poll_answer_handlers = + register_handler(self.poll_answer_handlers, h); + self + } + + /// Starts your bot with the default parameters. + /// + /// The default parameters are a long polling update listener and log all + /// errors produced by this listener). + pub async fn dispatch(&'a self) { + self.dispatch_with_listener( + update_listeners::polling_default(Arc::clone(&self.bot)), + &LoggingErrorHandler::new("An error from the update listener"), + ) + .await; + } + + /// Starts your bot with custom `update_listener` and + /// `update_listener_error_handler`. + pub async fn dispatch_with_listener( + &'a self, + update_listener: UListener, + update_listener_error_handler: &'a Eh, + ) where + UListener: UpdateListener + 'a, + Eh: ErrorHandler + 'a, + ListenerE: Debug, + { + let update_listener = Box::pin(update_listener); + + update_listener + .for_each_concurrent(None, move |update| async move { + log::trace!("Dispatcher received an update: {:?}", update); + + let update = match update { + Ok(update) => update, + Err(error) => { + update_listener_error_handler.handle_error(error).await; + return; + } + }; + + let update = + match self.handle(&self.update_handlers, update).await { + Some(update) => update, + None => return, + }; + + match update.kind { + UpdateKind::Message(message) => { + self.handle(&self.message_handlers, message).await; + } + UpdateKind::EditedMessage(message) => { + self.handle(&self.edited_message_handlers, message) + .await; + } + UpdateKind::ChannelPost(post) => { + self.handle(&self.channel_post_handlers, post).await; + } + UpdateKind::EditedChannelPost(post) => { + self.handle(&self.edited_channel_post_handlers, post) + .await; + } + UpdateKind::InlineQuery(query) => { + self.handle(&self.inline_query_handlers, query).await; + } + UpdateKind::ChosenInlineResult(result) => { + self.handle( + &self.chosen_inline_result_handlers, + result, + ) + .await; + } + UpdateKind::CallbackQuery(query) => { + self.handle(&self.callback_query_handlers, query).await; + } + UpdateKind::ShippingQuery(query) => { + self.handle(&self.shipping_query_handlers, query).await; + } + UpdateKind::PreCheckoutQuery(query) => { + self.handle(&self.pre_checkout_query_handlers, query) + .await; + } + UpdateKind::Poll(poll) => { + self.handle(&self.poll_handlers, poll).await; + } + UpdateKind::PollAnswer(answer) => { + self.handle(&self.poll_answer_handlers, answer).await; + } + } + }) + .await + } + + // Handles a single update. + #[allow(clippy::ptr_arg)] + async fn handle( + &self, + handlers: &Handlers<'a, Upd, HandlerE>, + update: Upd, + ) -> Option { + stream::iter(handlers) + .fold(Some(update), |acc, handler| { + async move { + // Option::and_then is not working here, because + // Middleware::handle is asynchronous. + match acc { + Some(update) => { + let DispatcherHandlerResult { next, result } = + handler + .handle_ctx(DispatcherHandlerCtx { + bot: Arc::clone(&self.bot), + update, + }) + .await; + + if let Err(error) = result { + self.handlers_error_handler + .handle_error(error) + .await + } + + next + } + None => None, + } + } + }) + .await + } +} + +/// Transforms Future into Future by applying an Into +/// conversion. +async fn intermediate_fut0(fut: impl Future) -> U +where + T: Into, +{ + fut.await.into() +} + +/// Transforms CtxHandler with Into> as a return +/// value into CtxHandler with DispatcherHandlerResult return value. +fn intermediate_fut1<'a, Upd, HandlerE, H, I>( + h: &'a H, +) -> impl CtxHandler< + DispatcherHandlerCtx, + DispatcherHandlerResult, +> + 'a +where + H: CtxHandler, I> + 'a, + I: Into> + 'a, + Upd: 'a, +{ + move |ctx| intermediate_fut0(h.handle_ctx(ctx)) +} + +/// Registers a single handler. +fn register_handler<'a, Upd, H, I, HandlerE>( + mut handlers: Handlers<'a, Upd, HandlerE>, + h: &'a H, +) -> Handlers<'a, Upd, HandlerE> +where + H: CtxHandler, I> + 'a, + I: Into> + 'a, + HandlerE: 'a, + Upd: 'a, +{ + handlers.push(Box::new(intermediate_fut1(h))); + handlers +} diff --git a/src/dispatching/dispatcher_handler_ctx.rs b/src/dispatching/dispatcher_handler_ctx.rs new file mode 100644 index 00000000..ccda15ff --- /dev/null +++ b/src/dispatching/dispatcher_handler_ctx.rs @@ -0,0 +1,168 @@ +use crate::{ + dispatching::dialogue::GetChatId, + requests::{ + DeleteMessage, EditMessageCaption, EditMessageText, ForwardMessage, + PinChatMessage, SendAnimation, SendAudio, SendContact, SendDocument, + SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendSticker, + SendVenue, SendVideo, SendVideoNote, SendVoice, + }, + types::{ChatId, ChatOrInlineMessage, InputFile, InputMedia, Message}, + Bot, +}; +use std::sync::Arc; + +/// A [`Dispatcher`]'s handler's context of a bot and an update. +/// +/// See [the module-level documentation for the design +/// overview](crate::dispatching). +/// +/// [`Dispatcher`]: crate::dispatching::Dispatcher +pub struct DispatcherHandlerCtx { + pub bot: Arc, + pub update: Upd, +} + +impl GetChatId for DispatcherHandlerCtx +where + Upd: GetChatId, +{ + fn chat_id(&self) -> i64 { + self.update.chat_id() + } +} + +impl DispatcherHandlerCtx { + pub fn answer(&self, text: T) -> SendMessage + where + T: Into, + { + self.bot.send_message(self.chat_id(), text) + } + + pub fn reply_to(&self, text: T) -> SendMessage + where + T: Into, + { + self.bot + .send_message(self.chat_id(), text) + .reply_to_message_id(self.update.id) + } + + pub fn answer_photo(&self, photo: InputFile) -> SendPhoto { + self.bot.send_photo(self.update.chat.id, photo) + } + + pub fn answer_audio(&self, audio: InputFile) -> SendAudio { + self.bot.send_audio(self.update.chat.id, audio) + } + + pub fn answer_animation(&self, animation: InputFile) -> SendAnimation { + self.bot.send_animation(self.update.chat.id, animation) + } + + pub fn answer_document(&self, document: InputFile) -> SendDocument { + self.bot.send_document(self.update.chat.id, document) + } + + pub fn answer_video(&self, video: InputFile) -> SendVideo { + self.bot.send_video(self.update.chat.id, video) + } + + pub fn answer_voice(&self, voice: InputFile) -> SendVoice { + self.bot.send_voice(self.update.chat.id, voice) + } + + pub fn answer_media_group(&self, media_group: T) -> SendMediaGroup + where + T: Into>, + { + self.bot.send_media_group(self.update.chat.id, media_group) + } + + pub fn answer_location( + &self, + latitude: f32, + longitude: f32, + ) -> SendLocation { + self.bot + .send_location(self.update.chat.id, latitude, longitude) + } + + pub fn answer_venue( + &self, + latitude: f32, + longitude: f32, + title: T, + address: U, + ) -> SendVenue + where + T: Into, + U: Into, + { + self.bot.send_venue( + self.update.chat.id, + latitude, + longitude, + title, + address, + ) + } + + pub fn answer_video_note(&self, video_note: InputFile) -> SendVideoNote { + self.bot.send_video_note(self.update.chat.id, video_note) + } + + pub fn answer_contact( + &self, + phone_number: T, + first_name: U, + ) -> SendContact + where + T: Into, + U: Into, + { + self.bot + .send_contact(self.chat_id(), phone_number, first_name) + } + + pub fn answer_sticker(&self, sticker: InputFile) -> SendSticker { + self.bot.send_sticker(self.update.chat.id, sticker) + } + + pub fn forward_to(&self, chat_id: T) -> ForwardMessage + where + T: Into, + { + self.bot + .forward_message(chat_id, self.update.chat.id, self.update.id) + } + + pub fn edit_message_text(&self, text: T) -> EditMessageText + where + T: Into, + { + self.bot.edit_message_text( + ChatOrInlineMessage::Chat { + chat_id: self.update.chat.id.into(), + message_id: self.update.id, + }, + text, + ) + } + + pub fn edit_message_caption(&self) -> EditMessageCaption { + self.bot.edit_message_caption(ChatOrInlineMessage::Chat { + chat_id: self.update.chat.id.into(), + message_id: self.update.id, + }) + } + + pub fn delete_message(&self) -> DeleteMessage { + self.bot.delete_message(self.update.chat.id, self.update.id) + } + + pub fn pin_message(&self) -> PinChatMessage { + self.bot + .pin_chat_message(self.update.chat.id, self.update.id) + } +} diff --git a/src/dispatching/dispatcher_handler_result.rs b/src/dispatching/dispatcher_handler_result.rs new file mode 100644 index 00000000..d7cacf22 --- /dev/null +++ b/src/dispatching/dispatcher_handler_result.rs @@ -0,0 +1,31 @@ +/// A result of a handler in [`Dispatcher`]. +/// +/// See [the module-level documentation for the design +/// overview](crate::dispatching). +/// +/// [`Dispatcher`]: crate::dispatching::Dispatcher +pub struct DispatcherHandlerResult { + pub next: Option, + pub result: Result<(), E>, +} + +impl DispatcherHandlerResult { + /// Creates new `DispatcherHandlerResult` that continues the pipeline. + pub fn next(update: Upd, result: Result<(), E>) -> Self { + Self { + next: Some(update), + result, + } + } + + /// Creates new `DispatcherHandlerResult` that terminates the pipeline. + pub fn exit(result: Result<(), E>) -> Self { + Self { next: None, result } + } +} + +impl From> for DispatcherHandlerResult { + fn from(result: Result<(), E>) -> Self { + Self::exit(result) + } +} diff --git a/src/dispatching/error_handlers.rs b/src/dispatching/error_handlers.rs new file mode 100644 index 00000000..c9e1aa44 --- /dev/null +++ b/src/dispatching/error_handlers.rs @@ -0,0 +1,151 @@ +use std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin}; + +/// An asynchronous handler of an error. +/// +/// See [the module-level documentation for the design +/// overview](crate::dispatching). +pub trait ErrorHandler { + #[must_use] + fn handle_error<'a>( + &'a self, + error: E, + ) -> Pin + 'a>> + where + E: 'a; +} + +impl ErrorHandler for F +where + F: Fn(E) -> Fut, + Fut: Future, +{ + fn handle_error<'a>( + &'a self, + error: E, + ) -> Pin + 'a>> + where + E: 'a, + { + Box::pin(async move { self(error).await }) + } +} + +/// A handler that silently ignores all errors. +/// +/// ## Example +/// ``` +/// # #[tokio::main] +/// # async fn main_() { +/// use teloxide::dispatching::{ErrorHandler, IgnoringErrorHandler}; +/// +/// IgnoringErrorHandler.handle_error(()).await; +/// IgnoringErrorHandler.handle_error(404).await; +/// IgnoringErrorHandler.handle_error("error").await; +/// # } +/// ``` +pub struct IgnoringErrorHandler; + +impl ErrorHandler for IgnoringErrorHandler { + fn handle_error<'a>( + &'a self, + _: E, + ) -> Pin + 'a>> + where + E: 'a, + { + Box::pin(async {}) + } +} + +/// A handler that silently ignores all errors that can never happen (e.g.: +/// [`!`] or [`Infallible`]). +/// +/// ## Examples +/// ``` +/// # #[tokio::main] +/// # async fn main_() { +/// use std::convert::{Infallible, TryInto}; +/// +/// use teloxide::dispatching::{ErrorHandler, IgnoringErrorHandlerSafe}; +/// +/// let result: Result = "str".try_into(); +/// match result { +/// Ok(string) => println!("{}", string), +/// Err(inf) => IgnoringErrorHandlerSafe.handle_error(inf).await, +/// } +/// +/// IgnoringErrorHandlerSafe.handle_error(return).await; // return type of `return` is `!` (aka never) +/// # } +/// ``` +/// +/// ```compile_fail +/// use teloxide::dispatching::{ErrorHandler, IgnoringErrorHandlerSafe}; +/// +/// IgnoringErrorHandlerSafe.handle_error(0); +/// ``` +/// +/// [`!`]: https://doc.rust-lang.org/std/primitive.never.html +/// [`Infallible`]: std::convert::Infallible +pub struct IgnoringErrorHandlerSafe; + +#[allow(unreachable_code)] +impl ErrorHandler for IgnoringErrorHandlerSafe { + fn handle_error<'a>( + &'a self, + _: Infallible, + ) -> Pin + 'a>> + where + Infallible: 'a, + { + Box::pin(async {}) + } +} + +/// A handler that log all errors passed into it. +/// +/// ## Example +/// ``` +/// # #[tokio::main] +/// # async fn main_() { +/// use teloxide::dispatching::{ErrorHandler, LoggingErrorHandler}; +/// +/// LoggingErrorHandler::default().handle_error(()).await; +/// LoggingErrorHandler::new("error").handle_error(404).await; +/// LoggingErrorHandler::new("error") +/// .handle_error("Invalid data type!") +/// .await; +/// # } +/// ``` +#[derive(Default)] +pub struct LoggingErrorHandler { + text: String, +} + +impl LoggingErrorHandler { + /// Creates `LoggingErrorHandler` with a meta text before a log. + /// + /// The logs will be printed in this format: `{text}: {:?}`. + #[must_use] + pub fn new(text: T) -> Self + where + T: Into, + { + Self { text: text.into() } + } +} + +impl ErrorHandler for LoggingErrorHandler +where + E: Debug, +{ + fn handle_error<'a>( + &'a self, + error: E, + ) -> Pin + 'a>> + where + E: 'a, + { + log::error!("{text}: {:?}", error, text = self.text); + Box::pin(async {}) + } +} diff --git a/src/dispatching/handler.rs b/src/dispatching/handler.rs deleted file mode 100644 index b40ac794..00000000 --- a/src/dispatching/handler.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::{future::Future, pin::Pin}; - -/// Continue or terminate a user session. -#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] -pub enum SessionState { - Continue(Session), - Terminate, -} - -/// A handler of a user session and an update. -/// -/// ## Returns -/// Returns [`SessionState::Continue(session)`] if it wants to be called again -/// after a new update, or [`SessionState::Terminate`] if not. -/// -/// [`SessionState::Continue(session)`]: -/// crate::dispatching::SessionState::Continue -/// [`SessionState::Terminate`]: crate::dispatching::SessionState::Terminate -pub trait Handler { - #[must_use] - fn handle<'a>( - &'a self, - session: Session, - update: U, - ) -> Pin> + 'a>> - where - Session: 'a, - U: 'a; -} - -/// The implementation of `Handler` for `Fn(Session, Update) -> Future>`. -impl Handler for F -where - F: Fn(Session, U) -> Fut, - Fut: Future>, -{ - fn handle<'a>( - &'a self, - session: Session, - update: U, - ) -> Pin + 'a>> - where - Session: 'a, - U: 'a, - { - Box::pin(async move { self(session, update).await }) - } -} diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index cfd4f87b..9f248b42 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -1,14 +1,120 @@ -//! Update dispatching. +//! Updates dispatching. +//! +//! The key type here is [`Dispatcher`]. It encapsulates [`Bot`], handlers for +//! [11 update kinds] (+ for [`Update`]) and [`ErrorHandler`] for them. When +//! [`Update`] is received from Telegram, the following steps are executed: +//! +//! 1. It is supplied into an appropriate handler (the first ones is those who +//! accept [`Update`]). +//! 2. If a handler failed, invoke [`ErrorHandler`] with the corresponding +//! error. +//! 3. If a handler has returned [`DispatcherHandlerResult`] with `None`, +//! terminate the pipeline, otherwise supply an update into the next handler +//! (back to step 1). +//! +//! The pipeline is executed until either all the registered handlers were +//! executed, or one of handlers has terminated the pipeline. That's simple! +//! +//! 1. Note that handlers implement [`CtxHandler`], which means that you are +//! able to supply [`DialogueDispatcher`] as a handler, since it implements +//! [`CtxHandler`] too! +//! 2. Note that you don't always need to return [`DispatcherHandlerResult`] +//! explicitly, because of automatic conversions. Just return `Result<(), E>` if +//! you want to terminate the pipeline (see the example below). +//! +//! # Examples +//! ### The ping-pong bot +//! +//! ```no_run +//! # #[tokio::main] +//! # async fn main_() { +//! use teloxide::prelude::*; +//! +//! // Setup logging here... +//! +//! // Create a dispatcher with a single message handler that answers "pong" +//! // to each incoming message. +//! Dispatcher::::new(Bot::from_env()) +//! .message_handler(&|ctx: DispatcherHandlerCtx| async move { +//! ctx.answer("pong").send().await?; +//! Ok(()) +//! }) +//! .dispatch() +//! .await; +//! # } +//! ``` +//! +//! [Full](https://github.com/teloxide/teloxide/blob/dev/examples/ping_pong_bot/) +//! +//! ### Multiple handlers +//! +//! ```no_run +//! # #[tokio::main] +//! # async fn main_() { +//! use teloxide::prelude::*; +//! +//! // Create a dispatcher with multiple handlers of different types. This will +//! // print One! and Two! on every incoming UpdateKind::Message. +//! Dispatcher::::new(Bot::from_env()) +//! // This is the first UpdateKind::Message handler, which will be called +//! // after the Update handler below. +//! .message_handler(&|ctx: DispatcherHandlerCtx| async move { +//! log::info!("Two!"); +//! DispatcherHandlerResult::next(ctx.update, Ok(())) +//! }) +//! // Remember: handler of Update are called first. +//! .update_handler(&|ctx: DispatcherHandlerCtx| async move { +//! log::info!("One!"); +//! DispatcherHandlerResult::next(ctx.update, Ok(())) +//! }) +//! // This handler will be called right after the first UpdateKind::Message +//! // handler, because it is registered after. +//! .message_handler(&|_ctx: DispatcherHandlerCtx| async move { +//! // The same as DispatcherHandlerResult::exit(Ok(())) +//! Ok(()) +//! }) +//! // This handler will never be called, because the UpdateKind::Message +//! // handler above terminates the pipeline. +//! .message_handler(&|ctx: DispatcherHandlerCtx| async move { +//! log::info!("This will never be printed!"); +//! DispatcherHandlerResult::next(ctx.update, Ok(())) +//! }) +//! .dispatch() +//! .await; +//! +//! // Note: if this bot receive, for example, UpdateKind::ChannelPost, it will +//! // only print "One!", because the UpdateKind::Message handlers will not be +//! // called. +//! # } +//! ``` +//! +//! [Full](https://github.com/teloxide/teloxide/blob/dev/examples/miltiple_handlers_bot/) +//! +//! For a bit more complicated example, please see [examples/dialogue_bot]. +//! +//! [`Dispatcher`]: crate::dispatching::Dispatcher +//! [11 update kinds]: crate::types::UpdateKind +//! [`Update`]: crate::types::Update +//! [`ErrorHandler`]: crate::dispatching::ErrorHandler +//! [`CtxHandler`]: crate::dispatching::CtxHandler +//! [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher +//! [`DispatcherHandlerResult`]: crate::dispatching::DispatcherHandlerResult +//! [`Bot`]: crate::Bot +//! [examples/dialogue_bot]: https://github.com/teloxide/teloxide/tree/dev/examples/dialogue_bot -/// If an update was handled by a dispatcher or not. -#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] -pub enum DispatchResult { - Handled, - Unhandled, -} - -pub mod chat; -mod handler; +mod ctx_handlers; +pub mod dialogue; +mod dispatcher; +mod dispatcher_handler_ctx; +mod dispatcher_handler_result; +mod error_handlers; pub mod update_listeners; -pub use handler::*; +pub use ctx_handlers::CtxHandler; +pub use dispatcher::Dispatcher; +pub use dispatcher_handler_ctx::DispatcherHandlerCtx; +pub use dispatcher_handler_result::DispatcherHandlerResult; +pub use error_handlers::{ + ErrorHandler, IgnoringErrorHandler, IgnoringErrorHandlerSafe, + LoggingErrorHandler, +}; diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 9a0722f6..0b1190e3 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -108,7 +108,7 @@ use crate::{ types::{AllowedUpdate, Update}, RequestError, }; -use std::{convert::TryInto, time::Duration}; +use std::{convert::TryInto, sync::Arc, time::Duration}; /// A generic update listener. pub trait UpdateListener: Stream> { @@ -119,7 +119,7 @@ impl UpdateListener for S where S: Stream> {} /// Returns a long polling update listener with the default configuration. /// /// See also: [`polling`](polling). -pub fn polling_default(bot: &Bot) -> impl UpdateListener + '_ { +pub fn polling_default(bot: Arc) -> impl UpdateListener { polling(bot, None, None, None) } @@ -136,11 +136,11 @@ pub fn polling_default(bot: &Bot) -> impl UpdateListener + '_ { /// /// [`GetUpdates`]: crate::requests::GetUpdates pub fn polling( - bot: &Bot, + bot: Arc, timeout: Option, limit: Option, allowed_updates: Option>, -) -> impl UpdateListener + '_ { +) -> impl UpdateListener { let timeout = timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")); @@ -155,9 +155,39 @@ pub fn polling( let updates = match req.send().await { Err(err) => vec![Err(err)], Ok(updates) => { + // Set offset to the last update's id + 1 if let Some(upd) = updates.last() { - offset = upd.id + 1; + let id: i32 = match upd { + Ok(ok) => ok.id, + Err((value, _)) => value["update_id"] + .as_i64() + .expect( + "The 'update_id' field must always exist in \ + Update", + ) + .try_into() + .expect("update_id must be i32"), + }; + + offset = id + 1; } + + let updates = updates + .into_iter() + .filter(|update| match update { + Err((value, error)) => { + log::error!("Cannot parse an update.\nError: {:?}\nValue: {}\n\ + This is a bug in teloxide, please open an issue here: \ + https://github.com/teloxide/teloxide/issues.", error, value); + false + } + Ok(_) => true, + }) + .map(|update| { + update.expect("See the previous .filter() call") + }) + .collect::>(); + updates.into_iter().map(Ok).collect::>() } }; diff --git a/src/errors.rs b/src/errors.rs index 58c0b987..d64d073b 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -348,9 +348,9 @@ pub enum ApiErrorKind { /// chat. /// /// May happen in methods: - /// 1. [`PinMessage`] + /// 1. [`PinChatMessage`] /// - /// [`PinMessage`]: crate::requests::PinMessage + /// [`PinChatMessage`]: crate::requests::PinChatMessage #[serde(rename = "Bad Request: not enough rights to pin a message")] NotEnoughRightsToPinMessage, diff --git a/src/lib.rs b/src/lib.rs index 69cf82fe..25b1b2a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,281 @@ +//! A full-featured framework that empowers you to easily build [Telegram bots] +//! using the [`async`/`.await`] syntax in [Rust]. It handles all the difficult +//! stuff so you can focus only on your business logic. +//! +//! ## Features +//! - **Type-safe.** teloxide leverages the Rust's type system with two serious +//! implications: resistance to human mistakes and tight integration with +//! IDEs. Write fast, avoid debugging as possible. +//! +//! - **Persistency.** By default, teloxide stores all user dialogues in RAM, +//! but you can store them somewhere else (for example, in DB) just by +//! implementing 2 functions. +//! +//! - **Convenient dialogues system.** Define a type-safe [finite automaton] +//! and transition functions to drive a user dialogue with ease (see the +//! examples below). +//! +//! - **Convenient API.** Automatic conversions are used to avoid boilerplate. +//! For example, functions accept `Into`, rather than `&str` or +//! `String`, so you can call them without `.to_string()`/`.as_str()`/etc. +//! +//! ## Getting started +//! 1. Create a new bot using [@Botfather] to get a token in the format +//! `123456789:blablabla`. 2. Initialise the `TELOXIDE_TOKEN` environmental +//! variable to your token: +//! ```bash +//! # Unix +//! $ export TELOXIDE_TOKEN=MyAwesomeToken +//! +//! # Windows +//! $ set TELOXITE_TOKEN=MyAwesomeToken +//! ``` +//! 3. Be sure that you are up to date: +//! ```bash +//! $ rustup update stable +//! ``` +//! +//! 4. Execute `cargo new my_bot`, enter the directory and put these lines into +//! your `Cargo.toml`: +//! ```toml +//! [dependencies] +//! teloxide = "0.1.0" +//! log = "0.4.8" +//! tokio = "0.2.11" +//! pretty_env_logger = "0.4.0" +//! ``` +//! +//! ## The ping-pong bot +//! This bot has a single message handler, which answers "pong" to each incoming +//! message: +//! +//! ([Full](https://github.com/teloxide/teloxide/blob/dev/examples/ping_pong_bot/src/main.rs)) +//! ```rust,no_run +//! use teloxide::prelude::*; +//! +//! # #[tokio::main] +//! # async fn main() { +//! teloxide::enable_logging!(); +//! log::info!("Starting the ping-pong bot!"); +//! +//! let bot = Bot::from_env(); +//! +//! Dispatcher::::new(bot) +//! .message_handler(&|ctx: DispatcherHandlerCtx| async move { +//! ctx.answer("pong").send().await?; +//! Ok(()) +//! }) +//! .dispatch() +//! .await; +//! # } +//! ``` +//! +//! ## Commands +//! Commands are defined similar to how we define CLI using [structopt]. This +//! bot says "I am a cat! Meow!" on `/meow`, generates a random number within +//! [0; 1) on `/generate`, and shows the usage guide on `/help`: +//! +//! ([Full](https://github.com/teloxide/teloxide/blob/dev/examples/simple_commands_bot/src/main.rs)) +//! ```rust,no_run +//! # use teloxide::{prelude::*, utils::command::BotCommand}; +//! # use rand::{thread_rng, Rng}; +//! // Imports are omitted... +//! +//! #[derive(BotCommand)] +//! #[command( +//! rename = "lowercase", +//! description = "These commands are supported:" +//! )] +//! enum Command { +//! #[command(description = "display this text.")] +//! Help, +//! #[command(description = "be a cat.")] +//! Meow, +//! #[command(description = "generate a random number within [0; 1).")] +//! Generate, +//! } +//! +//! async fn handle_command( +//! ctx: DispatcherHandlerCtx, +//! ) -> Result<(), RequestError> { +//! let text = match ctx.update.text() { +//! Some(text) => text, +//! None => { +//! log::info!("Received a message, but not text."); +//! return Ok(()); +//! } +//! }; +//! +//! let command = match Command::parse(text) { +//! Some((command, _)) => command, +//! None => { +//! log::info!("Received a text message, but not a command."); +//! return Ok(()); +//! } +//! }; +//! +//! match command { +//! Command::Help => ctx.answer(Command::descriptions()).send().await?, +//! Command::Generate => { +//! ctx.answer(thread_rng().gen_range(0.0, 1.0).to_string()) +//! .send() +//! .await? +//! } +//! Command::Meow => ctx.answer("I am a cat! Meow!").send().await?, +//! }; +//! +//! Ok(()) +//! } +//! +//! #[tokio::main] +//! async fn main() { +//! // Setup is omitted... +//! # teloxide::enable_logging!(); +//! # log::info!("Starting simple_commands_bot!"); +//! # +//! # let bot = Bot::from_env(); +//! # +//! # Dispatcher::::new(bot) +//! # .message_handler(&handle_command) +//! # .dispatch() +//! # .await; +//! } +//! ``` +//! +//! ## Guess a number +//! Wanna see more? This is a bot, which starts a game on each incoming message. +//! You must guess a number from 1 to 10 (inclusively): +//! +//! ([Full](https://github.com/teloxide/teloxide/blob/dev/examples/guess_a_number_bot/src/main.rs)) +//! ```rust,no_run +//! # #[macro_use] +//! # extern crate smart_default; +//! # use teloxide::prelude::*; +//! # use rand::{thread_rng, Rng}; +//! // Imports are omitted... +//! +//! #[derive(SmartDefault)] +//! enum Dialogue { +//! #[default] +//! Start, +//! ReceiveAttempt(u8), +//! } +//! async fn handle_message( +//! ctx: DialogueHandlerCtx, +//! ) -> Result, RequestError> { +//! match ctx.dialogue { +//! Dialogue::Start => { +//! ctx.answer( +//! "Let's play a game! Guess a number from 1 to 10 +//! (inclusively).", +//! ) +//! .send() +//! .await?; +//! next(Dialogue::ReceiveAttempt(thread_rng().gen_range(1, 11))) +//! } +//! Dialogue::ReceiveAttempt(secret) => match ctx.update.text() { +//! None => { +//! ctx.answer("Oh, please, send me a text message!") +//! .send() +//! .await?; +//! next(ctx.dialogue) +//! } +//! Some(text) => match text.parse::() { +//! Ok(attempt) => match attempt { +//! x if !(1..=10).contains(&x) => { +//! ctx.answer( +//! "Oh, please, send me a number in the range \ +//! [1; 10]!", +//! ) +//! .send() +//! .await?; +//! next(ctx.dialogue) +//! } +//! x if x == secret => { +//! ctx.answer("Congratulations! You won!") +//! .send() +//! .await?; +//! exit() +//! } +//! _ => { +//! ctx.answer("No.").send().await?; +//! next(ctx.dialogue) +//! } +//! }, +//! Err(_) => { +//! ctx.answer( +//! "Oh, please, send me a number in the range [1; \ +//! 10]!", +//! ) +//! .send() +//! .await?; +//! next(ctx.dialogue) +//! } +//! }, +//! }, +//! } +//! } +//! +//! #[tokio::main] +//! async fn main() { +//! # teloxide::enable_logging!(); +//! # log::info!("Starting guess_a_number_bot!"); +//! # let bot = Bot::from_env(); +//! // Setup is omitted... +//! +//! Dispatcher::new(bot) +//! .message_handler(&DialogueDispatcher::new(|ctx| async move { +//! handle_message(ctx) +//! .await +//! .expect("Something wrong with the bot!") +//! })) +//! .dispatch() +//! .await; +//! } +//! ``` +//! +//! Our [finite automaton], designating a user dialogue, cannot be in an invalid +//! state. See [examples/dialogue_bot] to see a bit more complicated bot with +//! dialogues. +//! +//! [See more examples](https://github.com/teloxide/teloxide/tree/dev/examples). +//! +//! ## Recommendations +//! +//! - Use this pattern: +//! +//! ```rust +//! #[tokio::main] +//! async fn main() { +//! run().await; +//! } +//! +//! async fn run() { +//! // Your logic here... +//! } +//! ``` +//! +//! Instead of this: +//! +//! ```rust +//! #[tokio::main] +//! async fn main() { +//! // Your logic here... +//! } +//! ``` +//! +//! The second one produces very strange compiler messages because of the +//! `#[tokio::main]` macro. However, the examples above use the second one for +//! brevity. +//! +//! [Telegram bots]: https://telegram.org/blog/bot-revolution +//! [`async`/`.await`]: https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html +//! [Rust]: https://www.rust-lang.org/ +//! [finite automaton]: https://en.wikipedia.org/wiki/Finite-state_machine +//! [examples/dialogue_bot]: https://github.com/teloxide/teloxide/blob/dev/examples/dialogue_bot/src/main.rs +//! [structopt]: https://docs.rs/structopt/0.3.9/structopt/ +//! [@Botfather]: https://t.me/botfather + #![doc( html_logo_url = "https://github.com/teloxide/teloxide/raw/dev/logo.svg", html_favicon_url = "https://github.com/teloxide/teloxide/raw/dev/ICON.png" @@ -12,6 +290,8 @@ mod net; mod bot; pub mod dispatching; +mod logging; +pub mod prelude; pub mod requests; pub mod types; pub mod utils; diff --git a/src/logging.rs b/src/logging.rs new file mode 100644 index 00000000..ccb4c7c1 --- /dev/null +++ b/src/logging.rs @@ -0,0 +1,52 @@ +/// Enables logging through [pretty-env-logger]. +/// +/// A logger will **only** print errors from teloxide and **all** logs from +/// your program. +/// +/// # Example +/// ```no_compile +/// teloxide::enable_logging!(); +/// ``` +/// +/// # Note +/// Calling this macro **is not mandatory**; you can setup if your own logger if +/// you want. +/// +/// [pretty-env-logger]: https://crates.io/crates/pretty_env_logger +#[macro_export] +macro_rules! enable_logging { + () => { + teloxide::enable_logging_with_filter!(log::LevelFilter::Trace); + }; +} + +/// Enables logging through [pretty-env-logger] with a custom filter for your +/// program. +/// +/// A logger will **only** print errors from teloxide and restrict logs from +/// your program by the specified filter. +/// +/// # Example +/// Allow printing all logs from your program up to [`LevelFilter::Debug`] (i.e. +/// do not print traces): +/// +/// ```no_compile +/// teloxide::enable_logging_with_filter!(log::LevelFilter::Debug); +/// ``` +/// +/// # Note +/// Calling this macro **is not mandatory**; you can setup if your own logger if +/// you want. +/// +/// [pretty-env-logger]: https://crates.io/crates/pretty_env_logger +/// [`LevelFilter::Debug`]: https://docs.rs/log/0.4.10/log/enum.LevelFilter.html +#[macro_export] +macro_rules! enable_logging_with_filter { + ($filter:expr) => { + pretty_env_logger::formatted_builder() + .write_style(pretty_env_logger::env_logger::WriteStyle::Auto) + .filter(Some(env!("CARGO_PKG_NAME")), $filter) + .filter(Some("teloxide"), log::LevelFilter::Error) + .init(); + }; +} diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 00000000..97b01f16 --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,14 @@ +//! Commonly used items. + +pub use crate::{ + dispatching::{ + dialogue::{ + exit, next, DialogueDispatcher, DialogueHandlerCtx, DialogueStage, + GetChatId, + }, + Dispatcher, DispatcherHandlerCtx, DispatcherHandlerResult, + }, + requests::{Request, ResponseResult}, + types::{Message, Update}, + Bot, RequestError, +}; diff --git a/src/requests/all/add_sticker_to_set.rs b/src/requests/all/add_sticker_to_set.rs index 760f4b54..119bd7e1 100644 --- a/src/requests/all/add_sticker_to_set.rs +++ b/src/requests/all/add_sticker_to_set.rs @@ -5,15 +5,15 @@ use crate::{ Bot, }; -use super::BotWrapper; use crate::requests::{Request, ResponseResult}; +use std::sync::Arc; /// Use this method to add a new sticker to a set created by the bot. /// /// [The official docs](https://core.telegram.org/bots/api#addstickertoset). -#[derive(PartialEq, Debug, Clone)] -pub struct AddStickerToSet<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct AddStickerToSet { + bot: Arc, user_id: i32, name: String, png_sticker: InputFile, @@ -22,7 +22,7 @@ pub struct AddStickerToSet<'a> { } #[async_trait::async_trait] -impl Request for AddStickerToSet<'_> { +impl Request for AddStickerToSet { type Output = True; async fn send(&self) -> ResponseResult { @@ -47,9 +47,9 @@ impl Request for AddStickerToSet<'_> { } } -impl<'a> AddStickerToSet<'a> { +impl AddStickerToSet { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, user_id: i32, name: N, png_sticker: InputFile, @@ -60,7 +60,7 @@ impl<'a> AddStickerToSet<'a> { E: Into, { Self { - bot: BotWrapper(bot), + bot, user_id, name: name.into(), png_sticker, diff --git a/src/requests/all/answer_callback_query.rs b/src/requests/all/answer_callback_query.rs index 38f0c856..a636e314 100644 --- a/src/requests/all/answer_callback_query.rs +++ b/src/requests/all/answer_callback_query.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::True, Bot, }; +use std::sync::Arc; /// Use this method to send answers to callback queries sent from [inline /// keyboards]. @@ -18,10 +18,10 @@ use crate::{ /// /// [inline keyboards]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct AnswerCallbackQuery<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct AnswerCallbackQuery { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, callback_query_id: String, text: Option, show_alert: Option, @@ -30,7 +30,7 @@ pub struct AnswerCallbackQuery<'a> { } #[async_trait::async_trait] -impl Request for AnswerCallbackQuery<'_> { +impl Request for AnswerCallbackQuery { type Output = True; async fn send(&self) -> ResponseResult { @@ -44,14 +44,14 @@ impl Request for AnswerCallbackQuery<'_> { } } -impl<'a> AnswerCallbackQuery<'a> { - pub(crate) fn new(bot: &'a Bot, callback_query_id: C) -> Self +impl AnswerCallbackQuery { + pub(crate) fn new(bot: Arc, callback_query_id: C) -> Self where C: Into, { let callback_query_id = callback_query_id.into(); Self { - bot: BotWrapper(bot), + bot, callback_query_id, text: None, show_alert: None, diff --git a/src/requests/all/answer_inline_query.rs b/src/requests/all/answer_inline_query.rs index 1236423a..ad75ea06 100644 --- a/src/requests/all/answer_inline_query.rs +++ b/src/requests/all/answer_inline_query.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{InlineQueryResult, True}, Bot, }; +use std::sync::Arc; /// Use this method to send answers to an inline query. /// @@ -14,10 +14,10 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#answerinlinequery). #[serde_with_macros::skip_serializing_none] -#[derive(PartialEq, Debug, Clone, Serialize)] -pub struct AnswerInlineQuery<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct AnswerInlineQuery { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, inline_query_id: String, results: Vec, cache_time: Option, @@ -28,7 +28,7 @@ pub struct AnswerInlineQuery<'a> { } #[async_trait::async_trait] -impl Request for AnswerInlineQuery<'_> { +impl Request for AnswerInlineQuery { type Output = True; async fn send(&self) -> ResponseResult { @@ -42,9 +42,9 @@ impl Request for AnswerInlineQuery<'_> { } } -impl<'a> AnswerInlineQuery<'a> { +impl AnswerInlineQuery { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, inline_query_id: I, results: R, ) -> Self @@ -55,7 +55,7 @@ impl<'a> AnswerInlineQuery<'a> { let inline_query_id = inline_query_id.into(); let results = results.into(); Self { - bot: BotWrapper(bot), + bot, inline_query_id, results, cache_time: None, diff --git a/src/requests/all/answer_pre_checkout_query.rs b/src/requests/all/answer_pre_checkout_query.rs index 8d8dd0fb..cdff1e28 100644 --- a/src/requests/all/answer_pre_checkout_query.rs +++ b/src/requests/all/answer_pre_checkout_query.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::True, Bot, }; +use std::sync::Arc; /// Once the user has confirmed their payment and shipping details, the Bot API /// sends the final confirmation in the form of an [`Update`] with the field @@ -18,17 +18,17 @@ use crate::{ /// /// [`Update`]: crate::types::Update #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct AnswerPreCheckoutQuery<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct AnswerPreCheckoutQuery { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, pre_checkout_query_id: String, ok: bool, error_message: Option, } #[async_trait::async_trait] -impl Request for AnswerPreCheckoutQuery<'_> { +impl Request for AnswerPreCheckoutQuery { type Output = True; async fn send(&self) -> ResponseResult { @@ -42,9 +42,9 @@ impl Request for AnswerPreCheckoutQuery<'_> { } } -impl<'a> AnswerPreCheckoutQuery<'a> { +impl AnswerPreCheckoutQuery { pub(crate) fn new

( - bot: &'a Bot, + bot: Arc, pre_checkout_query_id: P, ok: bool, ) -> Self @@ -53,7 +53,7 @@ impl<'a> AnswerPreCheckoutQuery<'a> { { let pre_checkout_query_id = pre_checkout_query_id.into(); Self { - bot: BotWrapper(bot), + bot, pre_checkout_query_id, ok, error_message: None, diff --git a/src/requests/all/answer_shipping_query.rs b/src/requests/all/answer_shipping_query.rs index 45435362..452c93ed 100644 --- a/src/requests/all/answer_shipping_query.rs +++ b/src/requests/all/answer_shipping_query.rs @@ -1,12 +1,13 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ShippingOption, True}, Bot, }; +use std::sync::Arc; + /// If you sent an invoice requesting a shipping address and the parameter /// `is_flexible` was specified, the Bot API will send an [`Update`] with a /// shipping_query field to the bot. Use this method to reply to shipping @@ -16,10 +17,10 @@ use crate::{ /// /// [`Update`]: crate::types::Update #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct AnswerShippingQuery<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct AnswerShippingQuery { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, shipping_query_id: String, ok: bool, shipping_options: Option>, @@ -27,7 +28,7 @@ pub struct AnswerShippingQuery<'a> { } #[async_trait::async_trait] -impl Request for AnswerShippingQuery<'_> { +impl Request for AnswerShippingQuery { type Output = True; async fn send(&self) -> ResponseResult { @@ -41,14 +42,14 @@ impl Request for AnswerShippingQuery<'_> { } } -impl<'a> AnswerShippingQuery<'a> { - pub(crate) fn new(bot: &'a Bot, shipping_query_id: S, ok: bool) -> Self +impl AnswerShippingQuery { + pub(crate) fn new(bot: Arc, shipping_query_id: S, ok: bool) -> Self where S: Into, { let shipping_query_id = shipping_query_id.into(); Self { - bot: BotWrapper(bot), + bot, shipping_query_id, ok, shipping_options: None, diff --git a/src/requests/all/bot_wrapper.rs b/src/requests/all/bot_wrapper.rs deleted file mode 100644 index 87857725..00000000 --- a/src/requests/all/bot_wrapper.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::Bot; -use std::ops::Deref; - -/// A wrapper that implements `Clone`, Copy, `PartialEq`, `Eq`, `Debug`, but -/// performs no copying, cloning and comparison. -/// -/// Used in the requests bodies. -#[derive(Debug)] -pub struct BotWrapper<'a>(pub &'a Bot); - -impl PartialEq for BotWrapper<'_> { - fn eq(&self, other: &BotWrapper<'_>) -> bool { - self.0.token() == other.0.token() - } -} - -impl Eq for BotWrapper<'_> {} - -impl<'a> Clone for BotWrapper<'a> { - fn clone(&self) -> BotWrapper<'a> { - Self(self.0) - } -} - -impl Copy for BotWrapper<'_> {} - -impl Deref for BotWrapper<'_> { - type Target = Bot; - - fn deref(&self) -> &Bot { - &self.0 - } -} diff --git a/src/requests/all/create_new_sticker_set.rs b/src/requests/all/create_new_sticker_set.rs index a72556f4..cfb98634 100644 --- a/src/requests/all/create_new_sticker_set.rs +++ b/src/requests/all/create_new_sticker_set.rs @@ -1,18 +1,18 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{InputFile, MaskPosition, True}, Bot, }; +use std::sync::Arc; /// Use this method to create new sticker set owned by a user. The bot will be /// able to edit the created sticker set. /// /// [The official docs](https://core.telegram.org/bots/api#createnewstickerset). -#[derive(PartialEq, Debug, Clone)] -pub struct CreateNewStickerSet<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct CreateNewStickerSet { + bot: Arc, user_id: i32, name: String, title: String, @@ -23,7 +23,7 @@ pub struct CreateNewStickerSet<'a> { } #[async_trait::async_trait] -impl Request for CreateNewStickerSet<'_> { +impl Request for CreateNewStickerSet { type Output = True; async fn send(&self) -> ResponseResult { @@ -52,9 +52,9 @@ impl Request for CreateNewStickerSet<'_> { } } -impl<'a> CreateNewStickerSet<'a> { +impl CreateNewStickerSet { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, user_id: i32, name: N, title: T, @@ -67,7 +67,7 @@ impl<'a> CreateNewStickerSet<'a> { E: Into, { Self { - bot: BotWrapper(bot), + bot, user_id, name: name.into(), title: title.into(), diff --git a/src/requests/all/delete_chat_photo.rs b/src/requests/all/delete_chat_photo.rs index af1b330e..d9ab17e1 100644 --- a/src/requests/all/delete_chat_photo.rs +++ b/src/requests/all/delete_chat_photo.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to delete a chat photo. Photos can't be changed for private /// chats. The bot must be an administrator in the chat for this to work and @@ -14,15 +14,15 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#deletechatphoto). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct DeleteChatPhoto<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct DeleteChatPhoto { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, } #[async_trait::async_trait] -impl Request for DeleteChatPhoto<'_> { +impl Request for DeleteChatPhoto { type Output = True; async fn send(&self) -> ResponseResult { @@ -36,16 +36,13 @@ impl Request for DeleteChatPhoto<'_> { } } -impl<'a> DeleteChatPhoto<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl DeleteChatPhoto { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); - Self { - bot: BotWrapper(bot), - chat_id, - } + Self { bot, chat_id } } /// Unique identifier for the target chat or username of the target channel diff --git a/src/requests/all/delete_chat_sticker_set.rs b/src/requests/all/delete_chat_sticker_set.rs index b10962dc..0ad7ead9 100644 --- a/src/requests/all/delete_chat_sticker_set.rs +++ b/src/requests/all/delete_chat_sticker_set.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to delete a group sticker set from a supergroup. /// @@ -19,15 +19,15 @@ use crate::{ /// /// [`Bot::get_chat`]: crate::Bot::get_chat #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct DeleteChatStickerSet<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct DeleteChatStickerSet { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, } #[async_trait::async_trait] -impl Request for DeleteChatStickerSet<'_> { +impl Request for DeleteChatStickerSet { type Output = True; async fn send(&self) -> ResponseResult { @@ -41,16 +41,13 @@ impl Request for DeleteChatStickerSet<'_> { } } -impl<'a> DeleteChatStickerSet<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl DeleteChatStickerSet { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); - Self { - bot: BotWrapper(bot), - chat_id, - } + Self { bot, chat_id } } /// Unique identifier for the target chat or username of the target diff --git a/src/requests/all/delete_message.rs b/src/requests/all/delete_message.rs index 0dbb88c8..cf28eb23 100644 --- a/src/requests/all/delete_message.rs +++ b/src/requests/all/delete_message.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to delete a message, including service messages. /// @@ -24,16 +24,16 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#deletemessage). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct DeleteMessage<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct DeleteMessage { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, message_id: i32, } #[async_trait::async_trait] -impl Request for DeleteMessage<'_> { +impl Request for DeleteMessage { type Output = True; async fn send(&self) -> ResponseResult { @@ -47,14 +47,14 @@ impl Request for DeleteMessage<'_> { } } -impl<'a> DeleteMessage<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, message_id: i32) -> Self +impl DeleteMessage { + pub(crate) fn new(bot: Arc, chat_id: C, message_id: i32) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, message_id, } diff --git a/src/requests/all/delete_sticker_from_set.rs b/src/requests/all/delete_sticker_from_set.rs index 111622f1..41f41f80 100644 --- a/src/requests/all/delete_sticker_from_set.rs +++ b/src/requests/all/delete_sticker_from_set.rs @@ -1,26 +1,26 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::True, Bot, }; +use std::sync::Arc; /// Use this method to delete a sticker from a set created by the bot. /// /// [The official docs](https://core.telegram.org/bots/api#deletestickerfromset). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct DeleteStickerFromSet<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct DeleteStickerFromSet { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, sticker: String, } #[async_trait::async_trait] -impl Request for DeleteStickerFromSet<'_> { +impl Request for DeleteStickerFromSet { type Output = True; async fn send(&self) -> ResponseResult { @@ -34,16 +34,13 @@ impl Request for DeleteStickerFromSet<'_> { } } -impl<'a> DeleteStickerFromSet<'a> { - pub(crate) fn new(bot: &'a Bot, sticker: S) -> Self +impl DeleteStickerFromSet { + pub(crate) fn new(bot: Arc, sticker: S) -> Self where S: Into, { let sticker = sticker.into(); - Self { - bot: BotWrapper(bot), - sticker, - } + Self { bot, sticker } } /// File identifier of the sticker. diff --git a/src/requests/all/delete_webhook.rs b/src/requests/all/delete_webhook.rs index 10511a1b..dbfcda20 100644 --- a/src/requests/all/delete_webhook.rs +++ b/src/requests/all/delete_webhook.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::True, Bot, }; +use std::sync::Arc; /// Use this method to remove webhook integration if you decide to switch back /// to [Bot::get_updates]. @@ -15,14 +15,14 @@ use crate::{ /// /// [Bot::get_updates]: crate::Bot::get_updates #[serde_with_macros::skip_serializing_none] -#[derive(Copy, Eq, PartialEq, Debug, Clone, Serialize)] -pub struct DeleteWebhook<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct DeleteWebhook { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, } #[async_trait::async_trait] -impl Request for DeleteWebhook<'_> { +impl Request for DeleteWebhook { type Output = True; #[allow(clippy::trivially_copy_pass_by_ref)] @@ -37,10 +37,8 @@ impl Request for DeleteWebhook<'_> { } } -impl<'a> DeleteWebhook<'a> { - pub(crate) fn new(bot: &'a Bot) -> Self { - Self { - bot: BotWrapper(bot), - } +impl DeleteWebhook { + pub(crate) fn new(bot: Arc) -> Self { + Self { bot } } } diff --git a/src/requests/all/edit_message_caption.rs b/src/requests/all/edit_message_caption.rs index e873d2dc..6a5d0e1d 100644 --- a/src/requests/all/edit_message_caption.rs +++ b/src/requests/all/edit_message_caption.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message, ParseMode}, Bot, }; +use std::sync::Arc; /// Use this method to edit captions of messages. /// @@ -18,10 +18,10 @@ use crate::{ /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct EditMessageCaption<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct EditMessageCaption { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, #[serde(flatten)] chat_or_inline_message: ChatOrInlineMessage, caption: Option, @@ -30,7 +30,7 @@ pub struct EditMessageCaption<'a> { } #[async_trait::async_trait] -impl Request for EditMessageCaption<'_> { +impl Request for EditMessageCaption { type Output = Message; async fn send(&self) -> ResponseResult { @@ -44,13 +44,13 @@ impl Request for EditMessageCaption<'_> { } } -impl<'a> EditMessageCaption<'a> { +impl EditMessageCaption { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, ) -> Self { Self { - bot: BotWrapper(bot), + bot, chat_or_inline_message, caption: None, parse_mode: None, diff --git a/src/requests/all/edit_message_live_location.rs b/src/requests/all/edit_message_live_location.rs index 1362366c..1e6bf9b1 100644 --- a/src/requests/all/edit_message_live_location.rs +++ b/src/requests/all/edit_message_live_location.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message}, Bot, }; +use std::sync::Arc; /// Use this method to edit live location messages. /// @@ -20,10 +20,10 @@ use crate::{ /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True #[serde_with_macros::skip_serializing_none] -#[derive(PartialEq, Debug, Clone, Serialize)] -pub struct EditMessageLiveLocation<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct EditMessageLiveLocation { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, #[serde(flatten)] chat_or_inline_message: ChatOrInlineMessage, latitude: f32, @@ -32,7 +32,7 @@ pub struct EditMessageLiveLocation<'a> { } #[async_trait::async_trait] -impl Request for EditMessageLiveLocation<'_> { +impl Request for EditMessageLiveLocation { type Output = Message; async fn send(&self) -> ResponseResult { @@ -46,15 +46,15 @@ impl Request for EditMessageLiveLocation<'_> { } } -impl<'a> EditMessageLiveLocation<'a> { +impl EditMessageLiveLocation { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, latitude: f32, longitude: f32, ) -> Self { Self { - bot: BotWrapper(bot), + bot, chat_or_inline_message, latitude, longitude, diff --git a/src/requests/all/edit_message_media.rs b/src/requests/all/edit_message_media.rs index 685a0356..31cd350d 100644 --- a/src/requests/all/edit_message_media.rs +++ b/src/requests/all/edit_message_media.rs @@ -1,10 +1,10 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatOrInlineMessage, InlineKeyboardMarkup, InputMedia, Message}, Bot, }; +use std::sync::Arc; /// Use this method to edit animation, audio, document, photo, or video /// messages. @@ -20,16 +20,16 @@ use crate::{ /// /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct EditMessageMedia<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct EditMessageMedia { + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, media: InputMedia, reply_markup: Option, } #[async_trait::async_trait] -impl Request for EditMessageMedia<'_> { +impl Request for EditMessageMedia { type Output = Message; async fn send(&self) -> ResponseResult { @@ -67,14 +67,14 @@ impl Request for EditMessageMedia<'_> { } } -impl<'a> EditMessageMedia<'a> { +impl EditMessageMedia { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, media: InputMedia, ) -> Self { Self { - bot: BotWrapper(bot), + bot, chat_or_inline_message, media, reply_markup: None, diff --git a/src/requests/all/edit_message_reply_markup.rs b/src/requests/all/edit_message_reply_markup.rs index 7c7de32e..9b60c224 100644 --- a/src/requests/all/edit_message_reply_markup.rs +++ b/src/requests/all/edit_message_reply_markup.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message}, Bot, }; +use std::sync::Arc; /// Use this method to edit only the reply markup of messages. /// @@ -18,17 +18,17 @@ use crate::{ /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct EditMessageReplyMarkup<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct EditMessageReplyMarkup { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, #[serde(flatten)] chat_or_inline_message: ChatOrInlineMessage, reply_markup: Option, } #[async_trait::async_trait] -impl Request for EditMessageReplyMarkup<'_> { +impl Request for EditMessageReplyMarkup { type Output = Message; async fn send(&self) -> ResponseResult { @@ -42,13 +42,13 @@ impl Request for EditMessageReplyMarkup<'_> { } } -impl<'a> EditMessageReplyMarkup<'a> { +impl EditMessageReplyMarkup { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, ) -> Self { Self { - bot: BotWrapper(bot), + bot, chat_or_inline_message, reply_markup: None, } diff --git a/src/requests/all/edit_message_text.rs b/src/requests/all/edit_message_text.rs index ffe71b4c..d517db38 100644 --- a/src/requests/all/edit_message_text.rs +++ b/src/requests/all/edit_message_text.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message, ParseMode}, Bot, }; +use std::sync::Arc; /// Use this method to edit text and game messages. /// @@ -18,10 +18,10 @@ use crate::{ /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct EditMessageText<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct EditMessageText { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, #[serde(flatten)] chat_or_inline_message: ChatOrInlineMessage, text: String, @@ -31,7 +31,7 @@ pub struct EditMessageText<'a> { } #[async_trait::async_trait] -impl Request for EditMessageText<'_> { +impl Request for EditMessageText { type Output = Message; async fn send(&self) -> ResponseResult { @@ -45,9 +45,9 @@ impl Request for EditMessageText<'_> { } } -impl<'a> EditMessageText<'a> { +impl EditMessageText { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, text: T, ) -> Self @@ -55,7 +55,7 @@ impl<'a> EditMessageText<'a> { T: Into, { Self { - bot: BotWrapper(bot), + bot, chat_or_inline_message, text: text.into(), parse_mode: None, diff --git a/src/requests/all/export_chat_invite_link.rs b/src/requests/all/export_chat_invite_link.rs index ec2f93eb..7e31cf96 100644 --- a/src/requests/all/export_chat_invite_link.rs +++ b/src/requests/all/export_chat_invite_link.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::ChatId, Bot, }; +use std::sync::Arc; /// Use this method to generate a new invite link for a chat; any previously /// generated link is revoked. @@ -28,15 +28,15 @@ use crate::{ /// [`Bot::export_chat_invite_link`]: crate::Bot::export_chat_invite_link /// [`Bot::get_chat`]: crate::Bot::get_chat #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct ExportChatInviteLink<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct ExportChatInviteLink { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, } #[async_trait::async_trait] -impl Request for ExportChatInviteLink<'_> { +impl Request for ExportChatInviteLink { type Output = String; /// Returns the new invite link as `String` on success. @@ -51,16 +51,13 @@ impl Request for ExportChatInviteLink<'_> { } } -impl<'a> ExportChatInviteLink<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl ExportChatInviteLink { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); - Self { - bot: BotWrapper(bot), - chat_id, - } + Self { bot, chat_id } } /// Unique identifier for the target chat or username of the target channel diff --git a/src/requests/all/forward_message.rs b/src/requests/all/forward_message.rs index 0b765f09..c37b71c6 100644 --- a/src/requests/all/forward_message.rs +++ b/src/requests/all/forward_message.rs @@ -1,21 +1,21 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, Message}, Bot, }; +use std::sync::Arc; /// Use this method to forward messages of any kind. /// /// [`The official docs`](https://core.telegram.org/bots/api#forwardmessage). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct ForwardMessage<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct ForwardMessage { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, from_chat_id: ChatId, disable_notification: Option, @@ -23,7 +23,7 @@ pub struct ForwardMessage<'a> { } #[async_trait::async_trait] -impl Request for ForwardMessage<'_> { +impl Request for ForwardMessage { type Output = Message; async fn send(&self) -> ResponseResult { @@ -37,9 +37,9 @@ impl Request for ForwardMessage<'_> { } } -impl<'a> ForwardMessage<'a> { +impl ForwardMessage { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, from_chat_id: F, message_id: i32, @@ -51,7 +51,7 @@ impl<'a> ForwardMessage<'a> { let chat_id = chat_id.into(); let from_chat_id = from_chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, from_chat_id, message_id, diff --git a/src/requests/all/get_chat.rs b/src/requests/all/get_chat.rs index ce939041..c726908e 100644 --- a/src/requests/all/get_chat.rs +++ b/src/requests/all/get_chat.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{Chat, ChatId}, Bot, }; +use std::sync::Arc; /// Use this method to get up to date information about the chat (current name /// of the user for one-on-one conversations, current username of a user, group @@ -14,15 +14,15 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#getchat). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetChat<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetChat { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, } #[async_trait::async_trait] -impl Request for GetChat<'_> { +impl Request for GetChat { type Output = Chat; async fn send(&self) -> ResponseResult { @@ -31,16 +31,13 @@ impl Request for GetChat<'_> { } } -impl<'a> GetChat<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl GetChat { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); - Self { - bot: BotWrapper(bot), - chat_id, - } + Self { bot, chat_id } } /// Unique identifier for the target chat or username of the target diff --git a/src/requests/all/get_chat_administrators.rs b/src/requests/all/get_chat_administrators.rs index bff1adb2..cc575a27 100644 --- a/src/requests/all/get_chat_administrators.rs +++ b/src/requests/all/get_chat_administrators.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, ChatMember}, Bot, }; +use std::sync::Arc; /// Use this method to get a list of administrators in a chat. /// @@ -15,15 +15,15 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#getchatadministrators). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetChatAdministrators<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetChatAdministrators { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, } #[async_trait::async_trait] -impl Request for GetChatAdministrators<'_> { +impl Request for GetChatAdministrators { type Output = Vec; /// On success, returns an array that contains information about all chat @@ -39,16 +39,13 @@ impl Request for GetChatAdministrators<'_> { } } -impl<'a> GetChatAdministrators<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl GetChatAdministrators { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); - Self { - bot: BotWrapper(bot), - chat_id, - } + Self { bot, chat_id } } /// Unique identifier for the target chat or username of the target diff --git a/src/requests/all/get_chat_member.rs b/src/requests/all/get_chat_member.rs index e3a4f190..6b5aabea 100644 --- a/src/requests/all/get_chat_member.rs +++ b/src/requests/all/get_chat_member.rs @@ -1,27 +1,27 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, ChatMember}, Bot, }; +use std::sync::Arc; /// Use this method to get information about a member of a chat. /// /// [The official docs](https://core.telegram.org/bots/api#getchatmember). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetChatMember<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetChatMember { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, user_id: i32, } #[async_trait::async_trait] -impl Request for GetChatMember<'_> { +impl Request for GetChatMember { type Output = ChatMember; async fn send(&self) -> ResponseResult { @@ -35,14 +35,14 @@ impl Request for GetChatMember<'_> { } } -impl<'a> GetChatMember<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, user_id: i32) -> Self +impl GetChatMember { + pub(crate) fn new(bot: Arc, chat_id: C, user_id: i32) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, user_id, } diff --git a/src/requests/all/get_chat_members_count.rs b/src/requests/all/get_chat_members_count.rs index 1d5e4c55..89deea55 100644 --- a/src/requests/all/get_chat_members_count.rs +++ b/src/requests/all/get_chat_members_count.rs @@ -1,26 +1,26 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::ChatId, Bot, }; +use std::sync::Arc; /// Use this method to get the number of members in a chat. /// /// [The official docs](https://core.telegram.org/bots/api#getchatmemberscount). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetChatMembersCount<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetChatMembersCount { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, } #[async_trait::async_trait] -impl Request for GetChatMembersCount<'_> { +impl Request for GetChatMembersCount { type Output = i32; async fn send(&self) -> ResponseResult { @@ -34,16 +34,13 @@ impl Request for GetChatMembersCount<'_> { } } -impl<'a> GetChatMembersCount<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl GetChatMembersCount { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); - Self { - bot: BotWrapper(bot), - chat_id, - } + Self { bot, chat_id } } /// Unique identifier for the target chat or username of the target diff --git a/src/requests/all/get_file.rs b/src/requests/all/get_file.rs index 4c470827..2694e20f 100644 --- a/src/requests/all/get_file.rs +++ b/src/requests/all/get_file.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::File, Bot, }; +use std::sync::Arc; /// Use this method to get basic info about a file and prepare it for /// downloading. @@ -28,15 +28,15 @@ use crate::{ /// [`File`]: crate::types::file /// [`GetFile`]: self::GetFile #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetFile<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetFile { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, file_id: String, } #[async_trait::async_trait] -impl Request for GetFile<'_> { +impl Request for GetFile { type Output = File; async fn send(&self) -> ResponseResult { @@ -45,13 +45,13 @@ impl Request for GetFile<'_> { } } -impl<'a> GetFile<'a> { - pub(crate) fn new(bot: &'a Bot, file_id: F) -> Self +impl GetFile { + pub(crate) fn new(bot: Arc, file_id: F) -> Self where F: Into, { Self { - bot: BotWrapper(bot), + bot, file_id: file_id.into(), } } diff --git a/src/requests/all/get_game_high_scores.rs b/src/requests/all/get_game_high_scores.rs index 04d43c5d..6026bfef 100644 --- a/src/requests/all/get_game_high_scores.rs +++ b/src/requests/all/get_game_high_scores.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatOrInlineMessage, GameHighScore}, Bot, }; +use std::sync::Arc; /// Use this method to get data for high score tables. /// @@ -21,17 +21,17 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#getgamehighscores). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetGameHighScores<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetGameHighScores { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, #[serde(flatten)] chat_or_inline_message: ChatOrInlineMessage, user_id: i32, } #[async_trait::async_trait] -impl Request for GetGameHighScores<'_> { +impl Request for GetGameHighScores { type Output = Vec; async fn send(&self) -> ResponseResult> { @@ -45,14 +45,14 @@ impl Request for GetGameHighScores<'_> { } } -impl<'a> GetGameHighScores<'a> { +impl GetGameHighScores { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, user_id: i32, ) -> Self { Self { - bot: BotWrapper(bot), + bot, chat_or_inline_message, user_id, } diff --git a/src/requests/all/get_me.rs b/src/requests/all/get_me.rs index e56ad3b6..857c8a6b 100644 --- a/src/requests/all/get_me.rs +++ b/src/requests/all/get_me.rs @@ -1,4 +1,3 @@ -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, @@ -6,18 +5,19 @@ use crate::{ Bot, }; use serde::Serialize; +use std::sync::Arc; /// A simple method for testing your bot's auth token. Requires no parameters. /// /// [The official docs](https://core.telegram.org/bots/api#getme). -#[derive(Eq, PartialEq, Debug, Clone, Copy, Serialize)] -pub struct GetMe<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetMe { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, } #[async_trait::async_trait] -impl Request for GetMe<'_> { +impl Request for GetMe { type Output = Me; /// Returns basic information about the bot. @@ -28,10 +28,8 @@ impl Request for GetMe<'_> { } } -impl<'a> GetMe<'a> { - pub(crate) fn new(bot: &'a Bot) -> Self { - Self { - bot: BotWrapper(bot), - } +impl GetMe { + pub(crate) fn new(bot: Arc) -> Self { + Self { bot } } } diff --git a/src/requests/all/get_sticker_set.rs b/src/requests/all/get_sticker_set.rs index a63911f8..8b1094a5 100644 --- a/src/requests/all/get_sticker_set.rs +++ b/src/requests/all/get_sticker_set.rs @@ -1,26 +1,26 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::StickerSet, Bot, }; +use std::sync::Arc; /// Use this method to get a sticker set. /// /// [The official docs](https://core.telegram.org/bots/api#getstickerset). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetStickerSet<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetStickerSet { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, name: String, } #[async_trait::async_trait] -impl Request for GetStickerSet<'_> { +impl Request for GetStickerSet { type Output = StickerSet; async fn send(&self) -> ResponseResult { @@ -34,16 +34,13 @@ impl Request for GetStickerSet<'_> { } } -impl<'a> GetStickerSet<'a> { - pub(crate) fn new(bot: &'a Bot, name: N) -> Self +impl GetStickerSet { + pub(crate) fn new(bot: Arc, name: N) -> Self where N: Into, { let name = name.into(); - Self { - bot: BotWrapper(bot), - name, - } + Self { bot, name } } /// Name of the sticker set. diff --git a/src/requests/all/get_updates.rs b/src/requests/all/get_updates.rs index c7311591..f3fbe0c4 100644 --- a/src/requests/all/get_updates.rs +++ b/src/requests/all/get_updates.rs @@ -1,12 +1,13 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{AllowedUpdate, Update}, - Bot, + Bot, RequestError, }; +use serde_json::Value; +use std::sync::Arc; /// Use this method to receive incoming updates using long polling ([wiki]). /// @@ -19,10 +20,10 @@ use crate::{ /// /// [wiki]: https://en.wikipedia.org/wiki/Push_technology#Long_polling #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetUpdates<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetUpdates { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, pub(crate) offset: Option, pub(crate) limit: Option, pub(crate) timeout: Option, @@ -30,24 +31,43 @@ pub struct GetUpdates<'a> { } #[async_trait::async_trait] -impl Request for GetUpdates<'_> { - type Output = Vec; +impl Request for GetUpdates { + type Output = Vec>; - async fn send(&self) -> ResponseResult> { - net::request_json( + /// Deserialize to `Vec>` instead of + /// `Vec`, because we want to parse the rest of updates even if our + /// library hasn't parsed one. + async fn send( + &self, + ) -> ResponseResult>> { + let value: Value = net::request_json( self.bot.client(), self.bot.token(), "getUpdates", &self, ) - .await + .await?; + + match value { + Value::Array(array) => Ok(array + .into_iter() + .map(|value| { + serde_json::from_str(&value.to_string()) + .map_err(|error| (value, error)) + }) + .collect()), + _ => Err(RequestError::InvalidJson( + serde_json::from_value::>(value) + .expect_err("get_update must return Value::Array"), + )), + } } } -impl<'a> GetUpdates<'a> { - pub(crate) fn new(bot: &'a Bot) -> Self { +impl GetUpdates { + pub(crate) fn new(bot: Arc) -> Self { Self { - bot: BotWrapper(bot), + bot, offset: None, limit: None, timeout: None, diff --git a/src/requests/all/get_user_profile_photos.rs b/src/requests/all/get_user_profile_photos.rs index bc9b79c3..c3939104 100644 --- a/src/requests/all/get_user_profile_photos.rs +++ b/src/requests/all/get_user_profile_photos.rs @@ -1,28 +1,28 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::UserProfilePhotos, Bot, }; +use std::sync::Arc; /// Use this method to get a list of profile pictures for a user. /// /// [The official docs](https://core.telegram.org/bots/api#getuserprofilephotos). #[serde_with_macros::skip_serializing_none] -#[derive(Copy, Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetUserProfilePhotos<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetUserProfilePhotos { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, user_id: i32, offset: Option, limit: Option, } #[async_trait::async_trait] -impl Request for GetUserProfilePhotos<'_> { +impl Request for GetUserProfilePhotos { type Output = UserProfilePhotos; async fn send(&self) -> ResponseResult { @@ -36,10 +36,10 @@ impl Request for GetUserProfilePhotos<'_> { } } -impl<'a> GetUserProfilePhotos<'a> { - pub(crate) fn new(bot: &'a Bot, user_id: i32) -> Self { +impl GetUserProfilePhotos { + pub(crate) fn new(bot: Arc, user_id: i32) -> Self { Self { - bot: BotWrapper(bot), + bot, user_id, offset: None, limit: None, diff --git a/src/requests/all/get_webhook_info.rs b/src/requests/all/get_webhook_info.rs index 88fa92f5..99ece7ea 100644 --- a/src/requests/all/get_webhook_info.rs +++ b/src/requests/all/get_webhook_info.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::WebhookInfo, Bot, }; +use std::sync::Arc; /// Use this method to get current webhook status. /// @@ -16,14 +16,14 @@ use crate::{ /// [The official docs](https://core.telegram.org/bots/api#getwebhookinfo). /// /// [`Bot::get_updates`]: crate::Bot::get_updates -#[derive(Copy, Eq, PartialEq, Debug, Clone, Serialize)] -pub struct GetWebhookInfo<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct GetWebhookInfo { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, } #[async_trait::async_trait] -impl Request for GetWebhookInfo<'_> { +impl Request for GetWebhookInfo { type Output = WebhookInfo; #[allow(clippy::trivially_copy_pass_by_ref)] @@ -38,10 +38,8 @@ impl Request for GetWebhookInfo<'_> { } } -impl<'a> GetWebhookInfo<'a> { - pub(crate) fn new(bot: &'a Bot) -> Self { - Self { - bot: BotWrapper(bot), - } +impl GetWebhookInfo { + pub(crate) fn new(bot: Arc) -> Self { + Self { bot } } } diff --git a/src/requests/all/kick_chat_member.rs b/src/requests/all/kick_chat_member.rs index c766071f..fb02d384 100644 --- a/src/requests/all/kick_chat_member.rs +++ b/src/requests/all/kick_chat_member.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to kick a user from a group, a supergroup or a channel. /// @@ -19,17 +19,17 @@ use crate::{ /// /// [unbanned]: crate::Bot::unban_chat_member #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct KickChatMember<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct KickChatMember { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, user_id: i32, until_date: Option, } #[async_trait::async_trait] -impl Request for KickChatMember<'_> { +impl Request for KickChatMember { type Output = True; async fn send(&self) -> ResponseResult { @@ -43,14 +43,14 @@ impl Request for KickChatMember<'_> { } } -impl<'a> KickChatMember<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, user_id: i32) -> Self +impl KickChatMember { + pub(crate) fn new(bot: Arc, chat_id: C, user_id: i32) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, user_id, until_date: None, diff --git a/src/requests/all/leave_chat.rs b/src/requests/all/leave_chat.rs index a0ccd4b4..d2dc5f14 100644 --- a/src/requests/all/leave_chat.rs +++ b/src/requests/all/leave_chat.rs @@ -1,26 +1,26 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method for your bot to leave a group, supergroup or channel. /// /// [The official docs](https://core.telegram.org/bots/api#leavechat). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct LeaveChat<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct LeaveChat { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, } #[async_trait::async_trait] -impl Request for LeaveChat<'_> { +impl Request for LeaveChat { type Output = True; async fn send(&self) -> ResponseResult { @@ -34,16 +34,13 @@ impl Request for LeaveChat<'_> { } } -impl<'a> LeaveChat<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl LeaveChat { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); - Self { - bot: BotWrapper(bot), - chat_id, - } + Self { bot, chat_id } } /// Unique identifier for the target chat or username of the target diff --git a/src/requests/all/mod.rs b/src/requests/all/mod.rs index 84acc2e1..7d8e6845 100644 --- a/src/requests/all/mod.rs +++ b/src/requests/all/mod.rs @@ -130,6 +130,3 @@ pub use stop_poll::*; pub use unban_chat_member::*; pub use unpin_chat_message::*; pub use upload_sticker_file::*; - -mod bot_wrapper; -use bot_wrapper::BotWrapper; diff --git a/src/requests/all/pin_chat_message.rs b/src/requests/all/pin_chat_message.rs index 6be8c7cf..256ddef1 100644 --- a/src/requests/all/pin_chat_message.rs +++ b/src/requests/all/pin_chat_message.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to pin a message in a group, a supergroup, or a channel. /// @@ -16,17 +16,17 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#pinchatmessage). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct PinChatMessage<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct PinChatMessage { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, message_id: i32, disable_notification: Option, } #[async_trait::async_trait] -impl Request for PinChatMessage<'_> { +impl Request for PinChatMessage { type Output = True; async fn send(&self) -> ResponseResult { @@ -40,14 +40,14 @@ impl Request for PinChatMessage<'_> { } } -impl<'a> PinChatMessage<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, message_id: i32) -> Self +impl PinChatMessage { + pub(crate) fn new(bot: Arc, chat_id: C, message_id: i32) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, message_id, disable_notification: None, diff --git a/src/requests/all/promote_chat_member.rs b/src/requests/all/promote_chat_member.rs index ddd46e93..32919b0f 100644 --- a/src/requests/all/promote_chat_member.rs +++ b/src/requests/all/promote_chat_member.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to promote or demote a user in a supergroup or a channel. /// @@ -16,10 +16,10 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#promotechatmember). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct PromoteChatMember<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct PromoteChatMember { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, user_id: i32, can_change_info: Option, @@ -33,7 +33,7 @@ pub struct PromoteChatMember<'a> { } #[async_trait::async_trait] -impl Request for PromoteChatMember<'_> { +impl Request for PromoteChatMember { type Output = True; async fn send(&self) -> ResponseResult { @@ -47,14 +47,14 @@ impl Request for PromoteChatMember<'_> { } } -impl<'a> PromoteChatMember<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, user_id: i32) -> Self +impl PromoteChatMember { + pub(crate) fn new(bot: Arc, chat_id: C, user_id: i32) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, user_id, can_change_info: None, diff --git a/src/requests/all/restrict_chat_member.rs b/src/requests/all/restrict_chat_member.rs index 918571a6..90dda304 100644 --- a/src/requests/all/restrict_chat_member.rs +++ b/src/requests/all/restrict_chat_member.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, ChatPermissions, True}, Bot, }; +use std::sync::Arc; /// Use this method to restrict a user in a supergroup. /// @@ -16,10 +16,10 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#restrictchatmember). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct RestrictChatMember<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct RestrictChatMember { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, user_id: i32, permissions: ChatPermissions, @@ -27,7 +27,7 @@ pub struct RestrictChatMember<'a> { } #[async_trait::async_trait] -impl Request for RestrictChatMember<'_> { +impl Request for RestrictChatMember { type Output = True; async fn send(&self) -> ResponseResult { @@ -41,9 +41,9 @@ impl Request for RestrictChatMember<'_> { } } -impl<'a> RestrictChatMember<'a> { +impl RestrictChatMember { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, user_id: i32, permissions: ChatPermissions, @@ -53,7 +53,7 @@ impl<'a> RestrictChatMember<'a> { { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, user_id, permissions, diff --git a/src/requests/all/send_animation.rs b/src/requests/all/send_animation.rs index 1584cc16..cbd40485 100644 --- a/src/requests/all/send_animation.rs +++ b/src/requests/all/send_animation.rs @@ -1,10 +1,10 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video /// without sound). @@ -13,9 +13,9 @@ use crate::{ /// may be changed in the future. /// /// [The official docs](https://core.telegram.org/bots/api#sendanimation). -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendAnimation<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendAnimation { + bot: Arc, pub chat_id: ChatId, pub animation: InputFile, pub duration: Option, @@ -30,7 +30,7 @@ pub struct SendAnimation<'a> { } #[async_trait::async_trait] -impl Request for SendAnimation<'_> { +impl Request for SendAnimation { type Output = Message; async fn send(&self) -> ResponseResult { @@ -67,13 +67,17 @@ impl Request for SendAnimation<'_> { } } -impl<'a> SendAnimation<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, animation: InputFile) -> Self +impl SendAnimation { + pub(crate) fn new( + bot: Arc, + chat_id: C, + animation: InputFile, + ) -> Self where C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), animation, duration: None, diff --git a/src/requests/all/send_audio.rs b/src/requests/all/send_audio.rs index 9be59300..829e41f9 100644 --- a/src/requests/all/send_audio.rs +++ b/src/requests/all/send_audio.rs @@ -1,10 +1,10 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send audio files, if you want Telegram clients to display /// them in the music player. @@ -17,9 +17,9 @@ use crate::{ /// [The official docs](https://core.telegram.org/bots/api#sendaudio). /// /// [`Bot::send_voice`]: crate::Bot::send_voice -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendAudio<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendAudio { + bot: Arc, chat_id: ChatId, audio: InputFile, caption: Option, @@ -34,7 +34,7 @@ pub struct SendAudio<'a> { } #[async_trait::async_trait] -impl Request for SendAudio<'_> { +impl Request for SendAudio { type Output = Message; async fn send(&self) -> ResponseResult { @@ -71,13 +71,13 @@ impl Request for SendAudio<'_> { } } -impl<'a> SendAudio<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, audio: InputFile) -> Self +impl SendAudio { + pub(crate) fn new(bot: Arc, chat_id: C, audio: InputFile) -> Self where C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), audio, caption: None, diff --git a/src/requests/all/send_chat_action.rs b/src/requests/all/send_chat_action.rs index 6718ae8f..1d7b0032 100644 --- a/src/requests/all/send_chat_action.rs +++ b/src/requests/all/send_chat_action.rs @@ -1,12 +1,12 @@ use serde::{Deserialize, Serialize}; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method when you need to tell the user that something is happening /// on the bot's side. @@ -26,10 +26,10 @@ use crate::{ /// [ImageBot]: https://t.me/imagebot /// [`Bot::send_chat_action`]: crate::Bot::send_chat_action #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SendChatAction<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SendChatAction { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, action: SendChatActionKind, } @@ -37,7 +37,7 @@ pub struct SendChatAction<'a> { /// A type of action used in [`SendChatAction`]. /// /// [`SendChatAction`]: crate::requests::SendChatAction -#[derive(PartialEq, Copy, Clone, Debug, Eq, Hash, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum SendChatActionKind { /// For [text messages](crate::Bot::send_message). @@ -72,7 +72,7 @@ pub enum SendChatActionKind { } #[async_trait::async_trait] -impl Request for SendChatAction<'_> { +impl Request for SendChatAction { type Output = True; async fn send(&self) -> ResponseResult { @@ -86,9 +86,9 @@ impl Request for SendChatAction<'_> { } } -impl<'a> SendChatAction<'a> { +impl SendChatAction { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, action: SendChatActionKind, ) -> Self @@ -96,7 +96,7 @@ impl<'a> SendChatAction<'a> { C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), action, } diff --git a/src/requests/all/send_contact.rs b/src/requests/all/send_contact.rs index 97cda878..1ad6d0b6 100644 --- a/src/requests/all/send_contact.rs +++ b/src/requests/all/send_contact.rs @@ -1,21 +1,21 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, Message, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send phone contacts. /// /// [The official docs](https://core.telegram.org/bots/api#sendcontact). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SendContact<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SendContact { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, phone_number: String, first_name: String, @@ -27,7 +27,7 @@ pub struct SendContact<'a> { } #[async_trait::async_trait] -impl Request for SendContact<'_> { +impl Request for SendContact { type Output = Message; async fn send(&self) -> ResponseResult { @@ -41,9 +41,9 @@ impl Request for SendContact<'_> { } } -impl<'a> SendContact<'a> { +impl SendContact { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, phone_number: P, first_name: F, @@ -57,7 +57,7 @@ impl<'a> SendContact<'a> { let phone_number = phone_number.into(); let first_name = first_name.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, phone_number, first_name, diff --git a/src/requests/all/send_document.rs b/src/requests/all/send_document.rs index a2175156..3e2cfd46 100644 --- a/src/requests/all/send_document.rs +++ b/src/requests/all/send_document.rs @@ -1,10 +1,10 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send general files. /// @@ -12,9 +12,9 @@ use crate::{ /// may be changed in the future. /// /// [The official docs](https://core.telegram.org/bots/api#senddocument). -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendDocument<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendDocument { + bot: Arc, chat_id: ChatId, document: InputFile, thumb: Option, @@ -26,7 +26,7 @@ pub struct SendDocument<'a> { } #[async_trait::async_trait] -impl Request for SendDocument<'_> { +impl Request for SendDocument { type Output = Message; async fn send(&self) -> ResponseResult { @@ -57,13 +57,13 @@ impl Request for SendDocument<'_> { } } -impl<'a> SendDocument<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, document: InputFile) -> Self +impl SendDocument { + pub(crate) fn new(bot: Arc, chat_id: C, document: InputFile) -> Self where C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), document, thumb: None, diff --git a/src/requests/all/send_game.rs b/src/requests/all/send_game.rs index bc3056aa..53555c06 100644 --- a/src/requests/all/send_game.rs +++ b/src/requests/all/send_game.rs @@ -1,21 +1,21 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{InlineKeyboardMarkup, Message}, Bot, }; +use std::sync::Arc; /// Use this method to send a game. /// /// [The official docs](https://core.telegram.org/bots/api#sendgame). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SendGame<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SendGame { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: i32, game_short_name: String, disable_notification: Option, @@ -24,7 +24,7 @@ pub struct SendGame<'a> { } #[async_trait::async_trait] -impl Request for SendGame<'_> { +impl Request for SendGame { type Output = Message; async fn send(&self) -> ResponseResult { @@ -38,14 +38,18 @@ impl Request for SendGame<'_> { } } -impl<'a> SendGame<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: i32, game_short_name: G) -> Self +impl SendGame { + pub(crate) fn new( + bot: Arc, + chat_id: i32, + game_short_name: G, + ) -> Self where G: Into, { let game_short_name = game_short_name.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, game_short_name, disable_notification: None, diff --git a/src/requests/all/send_invoice.rs b/src/requests/all/send_invoice.rs index 99fbab15..b9e710e3 100644 --- a/src/requests/all/send_invoice.rs +++ b/src/requests/all/send_invoice.rs @@ -1,21 +1,21 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{InlineKeyboardMarkup, LabeledPrice, Message}, Bot, }; +use std::sync::Arc; /// Use this method to send invoices. /// /// [The official docs](https://core.telegram.org/bots/api#sendinvoice). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SendInvoice<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SendInvoice { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: i32, title: String, description: String, @@ -42,7 +42,7 @@ pub struct SendInvoice<'a> { } #[async_trait::async_trait] -impl Request for SendInvoice<'_> { +impl Request for SendInvoice { type Output = Message; async fn send(&self) -> ResponseResult { @@ -56,10 +56,10 @@ impl Request for SendInvoice<'_> { } } -impl<'a> SendInvoice<'a> { +impl SendInvoice { #[allow(clippy::too_many_arguments)] pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: i32, title: T, description: D, @@ -86,7 +86,7 @@ impl<'a> SendInvoice<'a> { let currency = currency.into(); let prices = prices.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, title, description, diff --git a/src/requests/all/send_location.rs b/src/requests/all/send_location.rs index 6ce4060e..149d7a5c 100644 --- a/src/requests/all/send_location.rs +++ b/src/requests/all/send_location.rs @@ -1,21 +1,21 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, Message, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send point on the map. /// /// [The official docs](https://core.telegram.org/bots/api#sendlocation). #[serde_with_macros::skip_serializing_none] -#[derive(PartialEq, Debug, Clone, Serialize)] -pub struct SendLocation<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SendLocation { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, latitude: f32, longitude: f32, @@ -26,7 +26,7 @@ pub struct SendLocation<'a> { } #[async_trait::async_trait] -impl Request for SendLocation<'_> { +impl Request for SendLocation { type Output = Message; async fn send(&self) -> ResponseResult { @@ -40,9 +40,9 @@ impl Request for SendLocation<'_> { } } -impl<'a> SendLocation<'a> { +impl SendLocation { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, latitude: f32, longitude: f32, @@ -52,7 +52,7 @@ impl<'a> SendLocation<'a> { { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, latitude, longitude, diff --git a/src/requests/all/send_media_group.rs b/src/requests/all/send_media_group.rs index 6576da97..cae6604d 100644 --- a/src/requests/all/send_media_group.rs +++ b/src/requests/all/send_media_group.rs @@ -1,17 +1,17 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputMedia, Message}, Bot, }; +use std::sync::Arc; /// Use this method to send a group of photos or videos as an album. /// /// [The official docs](https://core.telegram.org/bots/api#sendmediagroup). -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendMediaGroup<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendMediaGroup { + bot: Arc, chat_id: ChatId, media: Vec, // TODO: InputMediaPhoto and InputMediaVideo disable_notification: Option, @@ -19,7 +19,7 @@ pub struct SendMediaGroup<'a> { } #[async_trait::async_trait] -impl Request for SendMediaGroup<'_> { +impl Request for SendMediaGroup { type Output = Vec; async fn send(&self) -> ResponseResult> { @@ -42,8 +42,8 @@ impl Request for SendMediaGroup<'_> { } } -impl<'a> SendMediaGroup<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, media: M) -> Self +impl SendMediaGroup { + pub(crate) fn new(bot: Arc, chat_id: C, media: M) -> Self where C: Into, M: Into>, @@ -51,7 +51,7 @@ impl<'a> SendMediaGroup<'a> { let chat_id = chat_id.into(); let media = media.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, media, disable_notification: None, diff --git a/src/requests/all/send_message.rs b/src/requests/all/send_message.rs index f19792fe..689d9671 100644 --- a/src/requests/all/send_message.rs +++ b/src/requests/all/send_message.rs @@ -1,21 +1,21 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, Message, ParseMode, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send text messages. /// /// [The official docs](https://core.telegram.org/bots/api#sendmessage). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SendMessage<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SendMessage { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, pub chat_id: ChatId, pub text: String, pub parse_mode: Option, @@ -26,7 +26,7 @@ pub struct SendMessage<'a> { } #[async_trait::async_trait] -impl Request for SendMessage<'_> { +impl Request for SendMessage { type Output = Message; async fn send(&self) -> ResponseResult { @@ -40,14 +40,14 @@ impl Request for SendMessage<'_> { } } -impl<'a> SendMessage<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, text: T) -> Self +impl SendMessage { + pub(crate) fn new(bot: Arc, chat_id: C, text: T) -> Self where C: Into, T: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), text: text.into(), parse_mode: None, diff --git a/src/requests/all/send_photo.rs b/src/requests/all/send_photo.rs index e75580a3..ea603eca 100644 --- a/src/requests/all/send_photo.rs +++ b/src/requests/all/send_photo.rs @@ -1,17 +1,17 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send photos. /// /// [The official docs](https://core.telegram.org/bots/api#sendphoto). -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendPhoto<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendPhoto { + bot: Arc, chat_id: ChatId, photo: InputFile, caption: Option, @@ -22,7 +22,7 @@ pub struct SendPhoto<'a> { } #[async_trait::async_trait] -impl Request for SendPhoto<'_> { +impl Request for SendPhoto { type Output = Message; async fn send(&self) -> ResponseResult { @@ -51,13 +51,13 @@ impl Request for SendPhoto<'_> { } } -impl<'a> SendPhoto<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, photo: InputFile) -> Self +impl SendPhoto { + pub(crate) fn new(bot: Arc, chat_id: C, photo: InputFile) -> Self where C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), photo, caption: None, diff --git a/src/requests/all/send_poll.rs b/src/requests/all/send_poll.rs index cec5a81a..a2fafd42 100644 --- a/src/requests/all/send_poll.rs +++ b/src/requests/all/send_poll.rs @@ -1,21 +1,21 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, Message, PollType, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send a native poll. /// /// [The official docs](https://core.telegram.org/bots/api#sendpoll). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SendPoll<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SendPoll { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, question: String, options: Vec, @@ -30,7 +30,7 @@ pub struct SendPoll<'a> { } #[async_trait::async_trait] -impl Request for SendPoll<'_> { +impl Request for SendPoll { type Output = Message; async fn send(&self) -> ResponseResult { @@ -44,9 +44,9 @@ impl Request for SendPoll<'_> { } } -impl<'a> SendPoll<'a> { +impl SendPoll { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, question: Q, options: O, @@ -60,7 +60,7 @@ impl<'a> SendPoll<'a> { let question = question.into(); let options = options.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, question, options, @@ -106,6 +106,7 @@ impl<'a> SendPoll<'a> { } /// `true`, if the poll needs to be anonymous, defaults to `true`. + #[allow(clippy::wrong_self_convention)] pub fn is_anonymous(mut self, val: T) -> Self where T: Into, @@ -145,6 +146,7 @@ impl<'a> SendPoll<'a> { /// Pass `true`, if the poll needs to be immediately closed. /// /// This can be useful for poll preview. + #[allow(clippy::wrong_self_convention)] pub fn is_closed(mut self, val: T) -> Self where T: Into, diff --git a/src/requests/all/send_sticker.rs b/src/requests/all/send_sticker.rs index 1cf07e23..e44dbb80 100644 --- a/src/requests/all/send_sticker.rs +++ b/src/requests/all/send_sticker.rs @@ -1,19 +1,19 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputFile, Message, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send static .WEBP or [animated] .TGS stickers. /// /// [The official docs](https://core.telegram.org/bots/api#sendsticker). /// /// [animated]: https://telegram.org/blog/animated-stickers -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendSticker<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendSticker { + bot: Arc, chat_id: ChatId, sticker: InputFile, disable_notification: Option, @@ -22,7 +22,7 @@ pub struct SendSticker<'a> { } #[async_trait::async_trait] -impl Request for SendSticker<'_> { +impl Request for SendSticker { type Output = Message; async fn send(&self) -> ResponseResult { @@ -47,13 +47,13 @@ impl Request for SendSticker<'_> { } } -impl<'a> SendSticker<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, sticker: InputFile) -> Self +impl SendSticker { + pub(crate) fn new(bot: Arc, chat_id: C, sticker: InputFile) -> Self where C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), sticker, disable_notification: None, diff --git a/src/requests/all/send_venue.rs b/src/requests/all/send_venue.rs index c0d06765..c049aeb2 100644 --- a/src/requests/all/send_venue.rs +++ b/src/requests/all/send_venue.rs @@ -1,21 +1,21 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, Message, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send information about a venue. /// /// [The official docs](https://core.telegram.org/bots/api#sendvenue). #[serde_with_macros::skip_serializing_none] -#[derive(PartialEq, Debug, Clone, Serialize)] -pub struct SendVenue<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SendVenue { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, latitude: f32, longitude: f32, @@ -29,7 +29,7 @@ pub struct SendVenue<'a> { } #[async_trait::async_trait] -impl Request for SendVenue<'_> { +impl Request for SendVenue { type Output = Message; async fn send(&self) -> ResponseResult { @@ -43,9 +43,9 @@ impl Request for SendVenue<'_> { } } -impl<'a> SendVenue<'a> { +impl SendVenue { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, latitude: f32, longitude: f32, @@ -61,7 +61,7 @@ impl<'a> SendVenue<'a> { let title = title.into(); let address = address.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, latitude, longitude, diff --git a/src/requests/all/send_video.rs b/src/requests/all/send_video.rs index 1671ad7c..8e89ef2e 100644 --- a/src/requests/all/send_video.rs +++ b/src/requests/all/send_video.rs @@ -1,10 +1,10 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send video files, Telegram clients support mp4 videos /// (other formats may be sent as Document). @@ -13,9 +13,9 @@ use crate::{ /// limit may be changed in the future. /// /// [The official docs](https://core.telegram.org/bots/api#sendvideo). -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendVideo<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendVideo { + bot: Arc, chat_id: ChatId, video: InputFile, duration: Option, @@ -31,7 +31,7 @@ pub struct SendVideo<'a> { } #[async_trait::async_trait] -impl Request for SendVideo<'_> { +impl Request for SendVideo { type Output = Message; async fn send(&self) -> ResponseResult { @@ -70,13 +70,13 @@ impl Request for SendVideo<'_> { } } -impl<'a> SendVideo<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, video: InputFile) -> Self +impl SendVideo { + pub(crate) fn new(bot: Arc, chat_id: C, video: InputFile) -> Self where C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), video, duration: None, diff --git a/src/requests/all/send_video_note.rs b/src/requests/all/send_video_note.rs index fbed9bbb..e3099c35 100644 --- a/src/requests/all/send_video_note.rs +++ b/src/requests/all/send_video_note.rs @@ -1,10 +1,10 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputFile, Message, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// As of [v.4.0], Telegram clients support rounded square mp4 videos of up to 1 /// minute long. Use this method to send video messages. @@ -12,9 +12,9 @@ use crate::{ /// [The official docs](https://core.telegram.org/bots/api#sendvideonote). /// /// [v.4.0]: https://telegram.org/blog/video-messages-and-telescope -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendVideoNote<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendVideoNote { + bot: Arc, chat_id: ChatId, video_note: InputFile, duration: Option, @@ -26,7 +26,7 @@ pub struct SendVideoNote<'a> { } #[async_trait::async_trait] -impl Request for SendVideoNote<'_> { +impl Request for SendVideoNote { type Output = Message; async fn send(&self) -> ResponseResult { @@ -57,9 +57,9 @@ impl Request for SendVideoNote<'_> { } } -impl<'a> SendVideoNote<'a> { +impl SendVideoNote { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, video_note: InputFile, ) -> Self @@ -67,7 +67,7 @@ impl<'a> SendVideoNote<'a> { C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), video_note, duration: None, diff --git a/src/requests/all/send_voice.rs b/src/requests/all/send_voice.rs index 00df1764..b5ef7b5d 100644 --- a/src/requests/all/send_voice.rs +++ b/src/requests/all/send_voice.rs @@ -1,10 +1,10 @@ -use super::BotWrapper; use crate::{ net, requests::{form_builder::FormBuilder, Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; +use std::sync::Arc; /// Use this method to send audio files, if you want Telegram clients to display /// the file as a playable voice message. @@ -18,9 +18,9 @@ use crate::{ /// /// [`Audio`]: crate::types::Audio /// [`Document`]: crate::types::Document -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct SendVoice<'a> { - bot: BotWrapper<'a>, +#[derive(Debug, Clone)] +pub struct SendVoice { + bot: Arc, chat_id: ChatId, voice: InputFile, caption: Option, @@ -32,7 +32,7 @@ pub struct SendVoice<'a> { } #[async_trait::async_trait] -impl Request for SendVoice<'_> { +impl Request for SendVoice { type Output = Message; async fn send(&self) -> ResponseResult { @@ -63,13 +63,13 @@ impl Request for SendVoice<'_> { } } -impl<'a> SendVoice<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, voice: InputFile) -> Self +impl SendVoice { + pub(crate) fn new(bot: Arc, chat_id: C, voice: InputFile) -> Self where C: Into, { Self { - bot: BotWrapper(bot), + bot, chat_id: chat_id.into(), voice, caption: None, diff --git a/src/requests/all/set_chat_administrator_custom_title.rs b/src/requests/all/set_chat_administrator_custom_title.rs index 94370bef..322f72f7 100644 --- a/src/requests/all/set_chat_administrator_custom_title.rs +++ b/src/requests/all/set_chat_administrator_custom_title.rs @@ -1,29 +1,29 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to set a custom title for an administrator in a supergroup /// promoted by the bot. /// /// [The official docs](https://core.telegram.org/bots/api#setchatadministratorcustomtitle). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetChatAdministratorCustomTitle<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetChatAdministratorCustomTitle { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, user_id: i32, custom_title: String, } #[async_trait::async_trait] -impl Request for SetChatAdministratorCustomTitle<'_> { +impl Request for SetChatAdministratorCustomTitle { type Output = True; async fn send(&self) -> ResponseResult { @@ -37,9 +37,9 @@ impl Request for SetChatAdministratorCustomTitle<'_> { } } -impl<'a> SetChatAdministratorCustomTitle<'a> { +impl SetChatAdministratorCustomTitle { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, user_id: i32, custom_title: CT, @@ -51,7 +51,7 @@ impl<'a> SetChatAdministratorCustomTitle<'a> { let chat_id = chat_id.into(); let custom_title = custom_title.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, user_id, custom_title, diff --git a/src/requests/all/set_chat_description.rs b/src/requests/all/set_chat_description.rs index 2c731483..5896584e 100644 --- a/src/requests/all/set_chat_description.rs +++ b/src/requests/all/set_chat_description.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to change the description of a group, a supergroup or a /// channel. @@ -16,16 +16,16 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#setchatdescription). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetChatDescription<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetChatDescription { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, description: Option, } #[async_trait::async_trait] -impl Request for SetChatDescription<'_> { +impl Request for SetChatDescription { type Output = True; async fn send(&self) -> ResponseResult { @@ -39,14 +39,14 @@ impl Request for SetChatDescription<'_> { } } -impl<'a> SetChatDescription<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl SetChatDescription { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, description: None, } diff --git a/src/requests/all/set_chat_permissions.rs b/src/requests/all/set_chat_permissions.rs index 2a16f863..4dec39f8 100644 --- a/src/requests/all/set_chat_permissions.rs +++ b/src/requests/all/set_chat_permissions.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, ChatPermissions, True}, Bot, }; +use std::sync::Arc; /// Use this method to set default chat permissions for all members. /// @@ -15,16 +15,16 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#setchatpermissions). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetChatPermissions<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetChatPermissions { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, permissions: ChatPermissions, } #[async_trait::async_trait] -impl Request for SetChatPermissions<'_> { +impl Request for SetChatPermissions { type Output = True; async fn send(&self) -> ResponseResult { @@ -38,9 +38,9 @@ impl Request for SetChatPermissions<'_> { } } -impl<'a> SetChatPermissions<'a> { +impl SetChatPermissions { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, permissions: ChatPermissions, ) -> Self @@ -49,7 +49,7 @@ impl<'a> SetChatPermissions<'a> { { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, permissions, } diff --git a/src/requests/all/set_chat_photo.rs b/src/requests/all/set_chat_photo.rs index 4279c7b1..0d152b4d 100644 --- a/src/requests/all/set_chat_photo.rs +++ b/src/requests/all/set_chat_photo.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, InputFile, True}, Bot, }; +use std::sync::Arc; /// Use this method to set a new profile photo for the chat. /// @@ -15,16 +15,16 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#setchatphoto). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetChatPhoto<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetChatPhoto { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, photo: InputFile, } #[async_trait::async_trait] -impl Request for SetChatPhoto<'_> { +impl Request for SetChatPhoto { type Output = True; async fn send(&self) -> ResponseResult { @@ -38,14 +38,14 @@ impl Request for SetChatPhoto<'_> { } } -impl<'a> SetChatPhoto<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, photo: InputFile) -> Self +impl SetChatPhoto { + pub(crate) fn new(bot: Arc, chat_id: C, photo: InputFile) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, photo, } diff --git a/src/requests/all/set_chat_sticker_set.rs b/src/requests/all/set_chat_sticker_set.rs index 1b2874e4..64d37017 100644 --- a/src/requests/all/set_chat_sticker_set.rs +++ b/src/requests/all/set_chat_sticker_set.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to set a new group sticker set for a supergroup. /// @@ -16,16 +16,16 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#setchatstickerset). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetChatStickerSet<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetChatStickerSet { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, sticker_set_name: String, } #[async_trait::async_trait] -impl Request for SetChatStickerSet<'_> { +impl Request for SetChatStickerSet { type Output = True; async fn send(&self) -> ResponseResult { @@ -39,9 +39,9 @@ impl Request for SetChatStickerSet<'_> { } } -impl<'a> SetChatStickerSet<'a> { +impl SetChatStickerSet { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_id: C, sticker_set_name: S, ) -> Self @@ -52,7 +52,7 @@ impl<'a> SetChatStickerSet<'a> { let chat_id = chat_id.into(); let sticker_set_name = sticker_set_name.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, sticker_set_name, } diff --git a/src/requests/all/set_chat_title.rs b/src/requests/all/set_chat_title.rs index 7b5349fa..6e05fc64 100644 --- a/src/requests/all/set_chat_title.rs +++ b/src/requests/all/set_chat_title.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to change the title of a chat. /// @@ -15,16 +15,16 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#setchattitle). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetChatTitle<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetChatTitle { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, title: String, } #[async_trait::async_trait] -impl Request for SetChatTitle<'_> { +impl Request for SetChatTitle { type Output = True; async fn send(&self) -> ResponseResult { @@ -38,8 +38,8 @@ impl Request for SetChatTitle<'_> { } } -impl<'a> SetChatTitle<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, title: T) -> Self +impl SetChatTitle { + pub(crate) fn new(bot: Arc, chat_id: C, title: T) -> Self where C: Into, T: Into, @@ -47,7 +47,7 @@ impl<'a> SetChatTitle<'a> { let chat_id = chat_id.into(); let title = title.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, title, } diff --git a/src/requests/all/set_game_score.rs b/src/requests/all/set_game_score.rs index e21152d6..679ca708 100644 --- a/src/requests/all/set_game_score.rs +++ b/src/requests/all/set_game_score.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatOrInlineMessage, Message}, Bot, }; +use std::sync::Arc; /// Use this method to set the score of the specified user in a game. /// @@ -20,10 +20,10 @@ use crate::{ /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetGameScore<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetGameScore { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, #[serde(flatten)] chat_or_inline_message: ChatOrInlineMessage, user_id: i32, @@ -33,7 +33,7 @@ pub struct SetGameScore<'a> { } #[async_trait::async_trait] -impl Request for SetGameScore<'_> { +impl Request for SetGameScore { type Output = Message; async fn send(&self) -> ResponseResult { @@ -47,15 +47,15 @@ impl Request for SetGameScore<'_> { } } -impl<'a> SetGameScore<'a> { +impl SetGameScore { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, user_id: i32, score: i32, ) -> Self { Self { - bot: BotWrapper(bot), + bot, chat_or_inline_message, user_id, score, diff --git a/src/requests/all/set_sticker_position_in_set.rs b/src/requests/all/set_sticker_position_in_set.rs index 11dfeceb..7bcea444 100644 --- a/src/requests/all/set_sticker_position_in_set.rs +++ b/src/requests/all/set_sticker_position_in_set.rs @@ -1,28 +1,28 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::True, Bot, }; +use std::sync::Arc; /// Use this method to move a sticker in a set created by the bot to a specific /// position. /// /// [The official docs](https://core.telegram.org/bots/api#setstickerpositioninset). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetStickerPositionInSet<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetStickerPositionInSet { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, sticker: String, position: i32, } #[async_trait::async_trait] -impl Request for SetStickerPositionInSet<'_> { +impl Request for SetStickerPositionInSet { type Output = True; async fn send(&self) -> ResponseResult { @@ -36,14 +36,14 @@ impl Request for SetStickerPositionInSet<'_> { } } -impl<'a> SetStickerPositionInSet<'a> { - pub(crate) fn new(bot: &'a Bot, sticker: S, position: i32) -> Self +impl SetStickerPositionInSet { + pub(crate) fn new(bot: Arc, sticker: S, position: i32) -> Self where S: Into, { let sticker = sticker.into(); Self { - bot: BotWrapper(bot), + bot, sticker, position, } diff --git a/src/requests/all/set_webhook.rs b/src/requests/all/set_webhook.rs index 1233e0cf..ad293b5c 100644 --- a/src/requests/all/set_webhook.rs +++ b/src/requests/all/set_webhook.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{AllowedUpdate, InputFile, True}, Bot, }; +use std::sync::Arc; /// Use this method to specify a url and receive incoming updates via an /// outgoing webhook. @@ -25,10 +25,10 @@ use crate::{ /// /// [`Update`]: crate::types::Update #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct SetWebhook<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct SetWebhook { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, url: String, certificate: Option, max_connections: Option, @@ -36,7 +36,7 @@ pub struct SetWebhook<'a> { } #[async_trait::async_trait] -impl Request for SetWebhook<'_> { +impl Request for SetWebhook { type Output = True; async fn send(&self) -> ResponseResult { @@ -50,14 +50,14 @@ impl Request for SetWebhook<'_> { } } -impl<'a> SetWebhook<'a> { - pub(crate) fn new(bot: &'a Bot, url: U) -> Self +impl SetWebhook { + pub(crate) fn new(bot: Arc, url: U) -> Self where U: Into, { let url = url.into(); Self { - bot: BotWrapper(bot), + bot, url, certificate: None, max_connections: None, diff --git a/src/requests/all/stop_message_live_location.rs b/src/requests/all/stop_message_live_location.rs index 859a3a84..2c970b2b 100644 --- a/src/requests/all/stop_message_live_location.rs +++ b/src/requests/all/stop_message_live_location.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message}, Bot, }; +use std::sync::Arc; /// Use this method to stop updating a live location message before /// `live_period` expires. @@ -19,17 +19,17 @@ use crate::{ /// [`Message`]: crate::types::Message /// [`True`]: crate::types::True #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct StopMessageLiveLocation<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct StopMessageLiveLocation { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, #[serde(flatten)] chat_or_inline_message: ChatOrInlineMessage, reply_markup: Option, } #[async_trait::async_trait] -impl Request for StopMessageLiveLocation<'_> { +impl Request for StopMessageLiveLocation { type Output = Message; async fn send(&self) -> ResponseResult { @@ -43,13 +43,13 @@ impl Request for StopMessageLiveLocation<'_> { } } -impl<'a> StopMessageLiveLocation<'a> { +impl StopMessageLiveLocation { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, chat_or_inline_message: ChatOrInlineMessage, ) -> Self { Self { - bot: BotWrapper(bot), + bot, chat_or_inline_message, reply_markup: None, } diff --git a/src/requests/all/stop_poll.rs b/src/requests/all/stop_poll.rs index 6e99b6c5..f0d97b48 100644 --- a/src/requests/all/stop_poll.rs +++ b/src/requests/all/stop_poll.rs @@ -1,28 +1,28 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, InlineKeyboardMarkup, Poll}, Bot, }; +use std::sync::Arc; /// Use this method to stop a poll which was sent by the bot. /// /// [The official docs](https://core.telegram.org/bots/api#stoppoll). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct StopPoll<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct StopPoll { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, message_id: i32, reply_markup: Option, } #[async_trait::async_trait] -impl Request for StopPoll<'_> { +impl Request for StopPoll { type Output = Poll; /// On success, the stopped [`Poll`] with the final results is returned. @@ -38,14 +38,14 @@ impl Request for StopPoll<'_> { .await } } -impl<'a> StopPoll<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, message_id: i32) -> Self +impl StopPoll { + pub(crate) fn new(bot: Arc, chat_id: C, message_id: i32) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, message_id, reply_markup: None, diff --git a/src/requests/all/unban_chat_member.rs b/src/requests/all/unban_chat_member.rs index 749f31cc..eb643301 100644 --- a/src/requests/all/unban_chat_member.rs +++ b/src/requests/all/unban_chat_member.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to unban a previously kicked user in a supergroup or /// channel. The user will **not** return to the group or channel automatically, @@ -15,16 +15,16 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#unbanchatmember). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct UnbanChatMember<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct UnbanChatMember { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, user_id: i32, } #[async_trait::async_trait] -impl Request for UnbanChatMember<'_> { +impl Request for UnbanChatMember { type Output = True; async fn send(&self) -> ResponseResult { @@ -38,14 +38,14 @@ impl Request for UnbanChatMember<'_> { } } -impl<'a> UnbanChatMember<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C, user_id: i32) -> Self +impl UnbanChatMember { + pub(crate) fn new(bot: Arc, chat_id: C, user_id: i32) -> Self where C: Into, { let chat_id = chat_id.into(); Self { - bot: BotWrapper(bot), + bot, chat_id, user_id, } diff --git a/src/requests/all/unpin_chat_message.rs b/src/requests/all/unpin_chat_message.rs index b4b42921..926719b2 100644 --- a/src/requests/all/unpin_chat_message.rs +++ b/src/requests/all/unpin_chat_message.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{ChatId, True}, Bot, }; +use std::sync::Arc; /// Use this method to unpin a message in a group, a supergroup, or a channel. /// @@ -16,15 +16,15 @@ use crate::{ /// /// [The official docs](https://core.telegram.org/bots/api#unpinchatmessage). #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct UnpinChatMessage<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct UnpinChatMessage { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, chat_id: ChatId, } #[async_trait::async_trait] -impl Request for UnpinChatMessage<'_> { +impl Request for UnpinChatMessage { type Output = True; async fn send(&self) -> ResponseResult { @@ -38,16 +38,13 @@ impl Request for UnpinChatMessage<'_> { } } -impl<'a> UnpinChatMessage<'a> { - pub(crate) fn new(bot: &'a Bot, chat_id: C) -> Self +impl UnpinChatMessage { + pub(crate) fn new(bot: Arc, chat_id: C) -> Self where C: Into, { let chat_id = chat_id.into(); - Self { - bot: BotWrapper(bot), - chat_id, - } + Self { bot, chat_id } } /// Unique identifier for the target chat or username of the target channel diff --git a/src/requests/all/upload_sticker_file.rs b/src/requests/all/upload_sticker_file.rs index 6183e2de..68015c1c 100644 --- a/src/requests/all/upload_sticker_file.rs +++ b/src/requests/all/upload_sticker_file.rs @@ -1,12 +1,12 @@ use serde::Serialize; -use super::BotWrapper; use crate::{ net, requests::{Request, ResponseResult}, types::{File, InputFile}, Bot, }; +use std::sync::Arc; /// Use this method to upload a .png file with a sticker for later use in /// [`Bot::create_new_sticker_set`] and [`Bot::add_sticker_to_set`] methods (can @@ -17,15 +17,15 @@ use crate::{ /// [`Bot::create_new_sticker_set`]: crate::Bot::create_new_sticker_set /// [`Bot::add_sticker_to_set`]: crate::Bot::add_sticker_to_set #[serde_with_macros::skip_serializing_none] -#[derive(Eq, PartialEq, Debug, Clone, Serialize)] -pub struct UploadStickerFile<'a> { +#[derive(Debug, Clone, Serialize)] +pub struct UploadStickerFile { #[serde(skip_serializing)] - bot: BotWrapper<'a>, + bot: Arc, user_id: i32, png_sticker: InputFile, } #[async_trait::async_trait] -impl Request for UploadStickerFile<'_> { +impl Request for UploadStickerFile { type Output = File; async fn send(&self) -> ResponseResult { @@ -39,14 +39,14 @@ impl Request for UploadStickerFile<'_> { } } -impl<'a> UploadStickerFile<'a> { +impl UploadStickerFile { pub(crate) fn new( - bot: &'a Bot, + bot: Arc, user_id: i32, png_sticker: InputFile, ) -> Self { Self { - bot: BotWrapper(bot), + bot, user_id, png_sticker, } diff --git a/src/types/animation.rs b/src/types/animation.rs index 92d7e9e6..54694665 100644 --- a/src/types/animation.rs +++ b/src/types/animation.rs @@ -75,9 +75,7 @@ mod tests { file_size: Some(3452), }), file_name: Some("some".to_string()), - mime_type: Some(MimeWrapper { - mime: "video/gif".parse().unwrap(), - }), + mime_type: Some(MimeWrapper("video/gif".parse().unwrap())), file_size: Some(6500), }; let actual = serde_json::from_str::(json).unwrap(); diff --git a/src/types/callback_game.rs b/src/types/callback_game.rs index f2129185..358cee0c 100644 --- a/src/types/callback_game.rs +++ b/src/types/callback_game.rs @@ -3,5 +3,9 @@ /// [The official docs](https://core.telegram.org/bots/api#callbackgame). use serde::{Deserialize, Serialize}; +/// A placeholder, currently holds no information. Use [@Botfather] to set up +/// your game. +/// +/// [@Botfather]: https://t.me/botfather #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct CallbackGame; diff --git a/src/types/callback_query.rs b/src/types/callback_query.rs index d49a428e..47cedb96 100644 --- a/src/types/callback_query.rs +++ b/src/types/callback_query.rs @@ -3,11 +3,13 @@ use serde::{Deserialize, Serialize}; use crate::types::{Message, User}; /// This object represents an incoming callback query from a callback button in -/// an [inline keyboard]. If the button that originated the query was attached -/// to a message sent by the bot, the field message will be present. If the -/// button was attached to a message sent via the bot (in [inline mode]), the -/// field `inline_message_id` will be present. Exactly one of the fields data or -/// `game_short_name` will be present. +/// an [inline keyboard]. +/// +/// If the button that originated the query was attached to a message sent by +/// the bot, the field message will be present. If the button was attached to a +/// message sent via the bot (in [inline mode]), the field `inline_message_id` +/// will be present. Exactly one of the fields data or `game_short_name` will be +/// present. /// /// [The official docs](https://core.telegram.org/bots/api#callbackquery). /// diff --git a/src/types/chat_permissions.rs b/src/types/chat_permissions.rs index a82b79d5..a39314ea 100644 --- a/src/types/chat_permissions.rs +++ b/src/types/chat_permissions.rs @@ -39,3 +39,18 @@ pub struct ChatPermissions { /// supergroups. pub can_pin_messages: Option, } + +impl Default for ChatPermissions { + fn default() -> Self { + Self { + can_send_messages: None, + can_send_media_messages: None, + can_send_polls: None, + can_send_other_messages: None, + can_add_web_page_previews: None, + can_change_info: None, + can_invite_users: None, + can_pin_messages: None, + } + } +} diff --git a/src/types/encrypted_credentials.rs b/src/types/encrypted_credentials.rs index cd13b846..7e467192 100644 --- a/src/types/encrypted_credentials.rs +++ b/src/types/encrypted_credentials.rs @@ -1,8 +1,10 @@ use serde::{Deserialize, Serialize}; /// Contains data required for decrypting and authenticating -/// [`EncryptedPassportElement`]. See the [Telegram Passport Documentation] for -/// a complete description of the data decryption and authentication processes. +/// [`EncryptedPassportElement`]. +/// +/// See the [Telegram Passport Documentation] for a complete description of the +/// data decryption and authentication processes. /// /// [The official docs](https://core.telegram.org/bots/api#encryptedcredentials). /// diff --git a/src/types/file.rs b/src/types/file.rs index 523e6170..385a3930 100644 --- a/src/types/file.rs +++ b/src/types/file.rs @@ -1,7 +1,8 @@ use serde::{Deserialize, Serialize}; -/// This object represents a file ready to be downloaded. The file can be -/// downloaded via the link `https://api.telegram.org/file/bot/`. +/// This object represents a file ready to be downloaded. +/// +/// The file can be downloaded via the link `https://api.telegram.org/file/bot/`. /// It is guaranteed that the link will be valid for at least 1 hour. When the /// link expires, a new one can be requested by calling [`Bot::get_file`]. /// diff --git a/src/types/force_reply.rs b/src/types/force_reply.rs index 6e3143ab..bd3fc44a 100644 --- a/src/types/force_reply.rs +++ b/src/types/force_reply.rs @@ -4,9 +4,10 @@ use crate::types::True; /// Upon receiving a message with this object, Telegram clients will display a /// reply interface to the user (act as if the user has selected the bot‘s -/// message and tapped ’Reply'). This can be extremely useful if you want to -/// create user-friendly step-by-step interfaces without having to sacrifice -/// [privacy mode]. +/// message and tapped ’Reply'). +/// +/// This can be extremely useful if you want to create user-friendly +/// step-by-step interfaces without having to sacrifice [privacy mode]. /// /// [The official docs](https://core.telegram.org/bots/api#forcereply). /// diff --git a/src/types/game.rs b/src/types/game.rs index 310b55d4..bd28d5c6 100644 --- a/src/types/game.rs +++ b/src/types/game.rs @@ -2,8 +2,10 @@ use serde::{Deserialize, Serialize}; use crate::types::{Animation, MessageEntity, PhotoSize}; -/// This object represents a game. Use [@Botfather] to create and edit games, -/// their short names will act as unique identifiers. +/// This object represents a game. +/// +/// Use [@Botfather] to create and edit games, their short names will act as +/// unique identifiers. /// /// [@Botfather]: https://t.me/botfather #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_keyboard_markup.rs b/src/types/inline_keyboard_markup.rs index 0a35e799..9511bc66 100644 --- a/src/types/inline_keyboard_markup.rs +++ b/src/types/inline_keyboard_markup.rs @@ -30,13 +30,9 @@ pub struct InlineKeyboardMarkup { /// "text".to_string(), /// "http://url.com".to_string(), /// ); -/// let keyboard = InlineKeyboardMarkup::new().append_row(vec![url_button]); +/// let keyboard = InlineKeyboardMarkup::default().append_row(vec![url_button]); /// ``` impl InlineKeyboardMarkup { - pub fn new() -> Self { - <_>::default() - } - pub fn append_row(mut self, buttons: Vec) -> Self { self.inline_keyboard.push(buttons); self @@ -70,7 +66,7 @@ mod tests { "url 2".to_string(), ); - let markup = InlineKeyboardMarkup::new() + let markup = InlineKeyboardMarkup::default() .append_row(vec![button1.clone(), button2.clone()]); let expected = InlineKeyboardMarkup { @@ -91,7 +87,7 @@ mod tests { "url 2".to_string(), ); - let markup = InlineKeyboardMarkup::new() + let markup = InlineKeyboardMarkup::default() .append_row(vec![button1.clone()]) .append_to_row(button2.clone(), 0); @@ -113,7 +109,7 @@ mod tests { "url 2".to_string(), ); - let markup = InlineKeyboardMarkup::new() + let markup = InlineKeyboardMarkup::default() .append_row(vec![button1.clone()]) .append_to_row(button2.clone(), 1); diff --git a/src/types/inline_query.rs b/src/types/inline_query.rs index 9740039d..8b2ab03d 100644 --- a/src/types/inline_query.rs +++ b/src/types/inline_query.rs @@ -2,8 +2,10 @@ use serde::{Deserialize, Serialize}; use crate::types::{Location, User}; -/// This object represents an incoming inline query. When the user sends an -/// empty query, your bot could return some default or trending results. +/// This object represents an incoming inline query. +/// +/// When the user sends an empty query, your bot could return some default or +/// trending results. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequery). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result.rs b/src/types/inline_query_result.rs index de429703..b4273dce 100644 --- a/src/types/inline_query_result.rs +++ b/src/types/inline_query_result.rs @@ -88,7 +88,7 @@ mod tests { audio_file_id: String::from("audio_file_id"), caption: Some(String::from("caption")), parse_mode: Some(ParseMode::HTML), - reply_markup: Some(InlineKeyboardMarkup::new()), + reply_markup: Some(InlineKeyboardMarkup::default()), input_message_content: Some(InputMessageContent::Text { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), diff --git a/src/types/inline_query_result_audio.rs b/src/types/inline_query_result_audio.rs index f229161a..9d9c1077 100644 --- a/src/types/inline_query_result_audio.rs +++ b/src/types/inline_query_result_audio.rs @@ -3,8 +3,10 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// Represents a link to an MP3 audio file. By default, this audio file will be -/// sent by the user. Alternatively, you can use `input_message_content` to send -/// a message with the specified content instead of the audio. +/// sent by the user. +/// +/// Alternatively, you can use `input_message_content` to send a message with +/// the specified content instead of the audio. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultaudio). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_cached_audio.rs b/src/types/inline_query_result_cached_audio.rs index 96111406..47c2b4de 100644 --- a/src/types/inline_query_result_cached_audio.rs +++ b/src/types/inline_query_result_cached_audio.rs @@ -2,8 +2,9 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to an MP3 audio file stored on the Telegram servers. By -/// default, this audio file will be sent by the user. Alternatively, you can +/// Represents a link to an MP3 audio file stored on the Telegram servers. +/// +/// By default, this audio file will be sent by the user. Alternatively, you can /// use `input_message_content` to send a message with the specified content /// instead of the audio. /// diff --git a/src/types/inline_query_result_cached_document.rs b/src/types/inline_query_result_cached_document.rs index 126a0e1b..b6c9acf6 100644 --- a/src/types/inline_query_result_cached_document.rs +++ b/src/types/inline_query_result_cached_document.rs @@ -2,10 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to a file stored on the Telegram servers. By default, this -/// file will be sent by the user with an optional caption. Alternatively, you -/// can use `input_message_content` to send a message with the specified content -/// instead of the file. +/// Represents a link to a file stored on the Telegram servers. +/// +/// By default, this file will be sent by the user with an optional caption. +/// Alternatively, you can use `input_message_content` to send a message with +/// the specified content instead of the file. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcacheddocument). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_cached_gif.rs b/src/types/inline_query_result_cached_gif.rs index 890f4746..5281cccb 100644 --- a/src/types/inline_query_result_cached_gif.rs +++ b/src/types/inline_query_result_cached_gif.rs @@ -2,8 +2,9 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to an animated GIF file stored on the Telegram servers. By -/// default, this animated GIF file will be sent by the user with an optional +/// Represents a link to an animated GIF file stored on the Telegram servers. +/// +/// By default, this animated GIF file will be sent by the user with an optional /// caption. Alternatively, you can use `input_message_content` to send a /// message with specified content instead of the animation. /// diff --git a/src/types/inline_query_result_cached_mpeg4_gif.rs b/src/types/inline_query_result_cached_mpeg4_gif.rs index 49fe5f7f..010d2fd1 100644 --- a/src/types/inline_query_result_cached_mpeg4_gif.rs +++ b/src/types/inline_query_result_cached_mpeg4_gif.rs @@ -3,10 +3,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// Represents a link to a video animation (H.264/MPEG-4 AVC video without -/// sound) stored on the Telegram servers. By default, this animated MPEG-4 file -/// will be sent by the user with an optional caption. Alternatively, you can -/// use `input_message_content` to send a message with the specified content -/// instead of the animation. +/// sound) stored on the Telegram servers. +/// +/// By default, this animated MPEG-4 file will be sent by the user with an +/// optional caption. Alternatively, you can use `input_message_content` to send +/// a message with the specified content instead of the animation. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedmpeg4gif). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_cached_photo.rs b/src/types/inline_query_result_cached_photo.rs index 57d1d849..d528e33c 100644 --- a/src/types/inline_query_result_cached_photo.rs +++ b/src/types/inline_query_result_cached_photo.rs @@ -2,10 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to a photo stored on the Telegram servers. By default, -/// this photo will be sent by the user with an optional caption. Alternatively, -/// you can use `input_message_content` to send a message with the specified -/// content instead of the photo. +/// Represents a link to a photo stored on the Telegram servers. +/// +/// By default, this photo will be sent by the user with an optional caption. +/// Alternatively, you can use `input_message_content` to send a message with +/// the specified content instead of the photo. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedphoto). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_cached_sticker.rs b/src/types/inline_query_result_cached_sticker.rs index 25e1cb3e..75437e5a 100644 --- a/src/types/inline_query_result_cached_sticker.rs +++ b/src/types/inline_query_result_cached_sticker.rs @@ -2,10 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent}; -/// Represents a link to a sticker stored on the Telegram servers. By default, -/// this sticker will be sent by the user. Alternatively, you can use -/// `input_message_content` to send a message with the specified content instead -/// of the sticker. +/// Represents a link to a sticker stored on the Telegram servers. +/// +/// By default, this sticker will be sent by the user. Alternatively, you can +/// use `input_message_content` to send a message with the specified content +/// instead of the sticker. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedsticker). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_cached_video.rs b/src/types/inline_query_result_cached_video.rs index d800efa1..57ba728e 100644 --- a/src/types/inline_query_result_cached_video.rs +++ b/src/types/inline_query_result_cached_video.rs @@ -2,10 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to a video file stored on the Telegram servers. By -/// default, this video file will be sent by the user with an optional caption. -/// Alternatively, you can use `input_message_content` to send a message with -/// the specified content instead of the video. +/// Represents a link to a video file stored on the Telegram servers. +/// +/// By default, this video file will be sent by the user with an optional +/// caption. Alternatively, you can use `input_message_content` to send a +/// message with the specified content instead of the video. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_cached_voice.rs b/src/types/inline_query_result_cached_voice.rs index 4a60020b..354f11f3 100644 --- a/src/types/inline_query_result_cached_voice.rs +++ b/src/types/inline_query_result_cached_voice.rs @@ -2,9 +2,10 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to a voice message stored on the Telegram servers. By -/// default, this voice message will be sent by the user. Alternatively, you can -/// use `input_message_content` to send a message with the specified content +/// Represents a link to a voice message stored on the Telegram servers. +/// +/// By default, this voice message will be sent by the user. Alternatively, you +/// can use `input_message_content` to send a message with the specified content /// instead of the voice message. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). diff --git a/src/types/inline_query_result_contact.rs b/src/types/inline_query_result_contact.rs index 4622d66e..819bbd75 100644 --- a/src/types/inline_query_result_contact.rs +++ b/src/types/inline_query_result_contact.rs @@ -2,9 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent}; -/// Represents a contact with a phone number. By default, this contact will be -/// sent by the user. Alternatively, you can use `input_message_content` to send -/// a message with the specified content instead of the contact. +/// Represents a contact with a phone number. +/// +/// By default, this contact will be sent by the user. Alternatively, you can +/// use `input_message_content` to send a message with the specified content +/// instead of the contact. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_document.rs b/src/types/inline_query_result_document.rs index 6063614a..248f53c7 100644 --- a/src/types/inline_query_result_document.rs +++ b/src/types/inline_query_result_document.rs @@ -2,10 +2,12 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to a file. By default, this file will be sent by the user -/// with an optional caption. Alternatively, you can use `input_message_content` -/// to send a message with the specified content instead of the file. Currently, -/// only **.PDF** and **.ZIP** files can be sent using this method. +/// Represents a link to a file. +/// +/// By default, this file will be sent by the user with an optional caption. +/// Alternatively, you can use `input_message_content` to send a message with +/// the specified content instead of the file. Currently, only **.PDF** and +/// **.ZIP** files can be sent using this method. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultdocument). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_gif.rs b/src/types/inline_query_result_gif.rs index b6c7bf1d..3f0f8500 100644 --- a/src/types/inline_query_result_gif.rs +++ b/src/types/inline_query_result_gif.rs @@ -2,10 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to an animated GIF file. By default, this animated GIF -/// file will be sent by the user with optional caption. Alternatively, you can -/// use `input_message_content` to send a message with the specified content -/// instead of the animation. +/// Represents a link to an animated GIF file. +/// +/// By default, this animated GIF file will be sent by the user with optional +/// caption. Alternatively, you can use `input_message_content` to send a +/// message with the specified content instead of the animation. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultgif). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_location.rs b/src/types/inline_query_result_location.rs index 5b88605a..e5ed5d4e 100644 --- a/src/types/inline_query_result_location.rs +++ b/src/types/inline_query_result_location.rs @@ -2,9 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent}; -/// Represents a location on a map. By default, the location will be sent by the -/// user. Alternatively, you can use `input_message_content` to send a message -/// with the specified content instead of the location. +/// Represents a location on a map. +/// +/// By default, the location will be sent by the user. Alternatively, you can +/// use `input_message_content` to send a message with the specified content +/// instead of the location. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultlocation). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_mpeg4_gif.rs b/src/types/inline_query_result_mpeg4_gif.rs index 0eb49890..d9f50e70 100644 --- a/src/types/inline_query_result_mpeg4_gif.rs +++ b/src/types/inline_query_result_mpeg4_gif.rs @@ -3,8 +3,10 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// Represents a link to a video animation (H.264/MPEG-4 AVC video without -/// sound). By default, this animated MPEG-4 file will be sent by the user with -/// optional caption. Alternatively, you can use `input_message_content` to send +/// sound). +/// +/// By default, this animated MPEG-4 file will be sent by the user with optional +/// caption. Alternatively, you can use `input_message_content` to send /// a message with the specified content instead of the animation. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultmpeg4gif). diff --git a/src/types/inline_query_result_photo.rs b/src/types/inline_query_result_photo.rs index b3a8eaca..8b08c267 100644 --- a/src/types/inline_query_result_photo.rs +++ b/src/types/inline_query_result_photo.rs @@ -2,10 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; -/// Represents a link to a photo. By default, this photo will be sent by the -/// user with optional caption. Alternatively, you can use -/// `input_message_content` to send a message with the specified content instead -/// of the photo. +/// Represents a link to a photo. +/// +/// By default, this photo will be sent by the user with optional caption. +/// Alternatively, you can use `input_message_content` to send a message with +/// the specified content instead of the photo. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultphoto). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_venue.rs b/src/types/inline_query_result_venue.rs index 94ef15db..46485a88 100644 --- a/src/types/inline_query_result_venue.rs +++ b/src/types/inline_query_result_venue.rs @@ -2,9 +2,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent}; -/// Represents a venue. By default, the venue will be sent by the user. -/// Alternatively, you can use `input_message_content` to send a message with -/// the specified content instead of the venue. +/// Represents a venue. +/// +/// By default, the venue will be sent by the user. Alternatively, you can use +/// `input_message_content` to send a message with the specified content instead +/// of the venue. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultvenue). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/inline_query_result_video.rs b/src/types/inline_query_result_video.rs index e0cea329..8df2ec7f 100644 --- a/src/types/inline_query_result_video.rs +++ b/src/types/inline_query_result_video.rs @@ -3,7 +3,9 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// Represents a link to a page containing an embedded video player or a video -/// file. By default, this video file will be sent by the user with an optional +/// file. +/// +/// By default, this video file will be sent by the user with an optional /// caption. Alternatively, you can use `input_messaage_content` to send a /// message with the specified content instead of the video. /// diff --git a/src/types/inline_query_result_voice.rs b/src/types/inline_query_result_voice.rs index 4ddae8e3..efd6aaa2 100644 --- a/src/types/inline_query_result_voice.rs +++ b/src/types/inline_query_result_voice.rs @@ -3,9 +3,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// Represents a link to a voice recording in an .ogg container encoded with -/// OPUS. By default, this voice recording will be sent by the user. -/// Alternatively, you can use `input_message_content` to send a message with -/// the specified content instead of the the voice message. +/// OPUS. +/// +/// By default, this voice recording will be sent by the user. Alternatively, +/// you can use `input_message_content` to send a message with the specified +/// content instead of the the voice message. /// /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultvoice). #[serde_with_macros::skip_serializing_none] diff --git a/src/types/keyboard_button.rs b/src/types/keyboard_button.rs index 05e6450a..d2152184 100644 --- a/src/types/keyboard_button.rs +++ b/src/types/keyboard_button.rs @@ -2,9 +2,10 @@ use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; use crate::types::{KeyboardButtonPollType, True}; -/// This object represents one button of the reply keyboard. For filter text -/// buttons String can be used instead of this object to specify text of the -/// button. +/// This object represents one button of the reply keyboard. +/// +/// For filter text buttons String can be used instead of this object to specify +/// text of the button. /// /// [The official docs](https://core.telegram.org/bots/api#keyboardbutton). #[serde_with_macros::skip_serializing_none] @@ -23,6 +24,28 @@ pub struct KeyboardButton { pub request: Option, } +impl KeyboardButton { + /// Creates `KeyboardButton` with the provided `text` and all the other + /// fields set to `None`. + pub fn new(text: T) -> Self + where + T: Into, + { + Self { + text: text.into(), + request: None, + } + } + + pub fn request(mut self, val: T) -> Self + where + T: Into>, + { + self.request = val.into(); + self + } +} + // Serialize + Deserialize are implemented by hand #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum ButtonRequest { diff --git a/src/types/login_url.rs b/src/types/login_url.rs index 373e7f6c..2e2b7f94 100644 --- a/src/types/login_url.rs +++ b/src/types/login_url.rs @@ -1,9 +1,11 @@ use serde::{Deserialize, Serialize}; /// This object represents a parameter of the inline keyboard button used to -/// automatically authorize a user. Serves as a great replacement for the -/// [Telegram Login Widget] when the user is coming from Telegram. All the user -/// needs to do is tap/click a button and confirm that they want to log in: +/// automatically authorize a user. +/// +/// Serves as a great replacement for the [Telegram Login Widget] when the user +/// is coming from Telegram. All the user needs to do is tap/click a button and +/// confirm that they want to log in: /// ///

/// diff --git a/src/types/message.rs b/src/types/message.rs index 9e0d0a73..b324493f 100644 --- a/src/types/message.rs +++ b/src/types/message.rs @@ -34,8 +34,7 @@ pub struct Message { pub enum MessageKind { Common { /// Sender, empty for messages sent to channels. - #[serde(flatten)] - from: Sender, + from: Option, #[serde(flatten)] forward_kind: ForwardKind, @@ -144,17 +143,6 @@ pub enum MessageKind { }, } -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub enum Sender { - /// Sender of a message from chat. - #[serde(rename = "from")] - User(User), - - /// Signature of a sender of a message from a channel. - #[serde(rename = "author_signature")] - Signature(String), -} - #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum ForwardedFrom { #[serde(rename = "forward_from")] @@ -340,8 +328,7 @@ mod getters { Pinned, SuccessfulPayment, SupergroupChatCreated, }, }, - Chat, ForwardedFrom, Message, MessageEntity, PhotoSize, Sender, True, - User, + Chat, ForwardedFrom, Message, MessageEntity, PhotoSize, True, User, }; /// Getters for [Message] fields from [telegram docs]. @@ -350,13 +337,17 @@ mod getters { /// [telegram docs]: https://core.telegram.org/bots/api#message impl Message { /// NOTE: this is getter for both `from` and `author_signature` - pub fn from(&self) -> Option<&Sender> { + pub fn from(&self) -> Option<&User> { match &self.kind { - Common { from, .. } => Some(from), + Common { from, .. } => from.as_ref(), _ => None, } } + pub fn chat_id(&self) -> i64 { + self.chat.id + } + /// NOTE: this is getter for both `forward_from` and /// `forward_sender_name` pub fn forward_from(&self) -> Option<&ForwardedFrom> { @@ -728,21 +719,21 @@ mod getters { } } - pub fn migrate_to_chat_id(&self) -> Option<&i64> { + pub fn migrate_to_chat_id(&self) -> Option { match &self.kind { Migrate { migrate_to_chat_id, .. - } => Some(migrate_to_chat_id), + } => Some(*migrate_to_chat_id), _ => None, } } - pub fn migrate_from_chat_id(&self) -> Option<&i64> { + pub fn migrate_from_chat_id(&self) -> Option { match &self.kind { Migrate { migrate_from_chat_id, .. - } => Some(migrate_from_chat_id), + } => Some(*migrate_from_chat_id), _ => None, } } diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs index 23e101e6..31627b12 100644 --- a/src/types/message_entity.rs +++ b/src/types/message_entity.rs @@ -2,8 +2,9 @@ use serde::{Deserialize, Serialize}; use crate::types::{Message, User}; -/// This object represents one special entity in a text message. For example, -/// hashtags, usernames, URLs, etc. +/// This object represents one special entity in a text message. +/// +/// For example, hashtags, usernames, URLs, etc. /// /// [The official docs](https://core.telegram.org/bots/api#messageentity). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] @@ -49,9 +50,7 @@ impl MessageEntity { #[cfg(test)] mod tests { use super::*; - use crate::types::{ - Chat, ChatKind, ForwardKind, MediaKind, MessageKind, Sender, - }; + use crate::types::{Chat, ChatKind, ForwardKind, MediaKind, MessageKind}; #[test] fn recursive_kind() { @@ -115,7 +114,7 @@ mod tests { photo: None, }, kind: MessageKind::Common { - from: Sender::User(User { + from: Some(User { id: 0, is_bot: false, first_name: "".to_string(), diff --git a/src/types/non_telegram_types/mime_wrapper.rs b/src/types/non_telegram_types/mime_wrapper.rs index 20222e1c..d45a2dfb 100644 --- a/src/types/non_telegram_types/mime_wrapper.rs +++ b/src/types/non_telegram_types/mime_wrapper.rs @@ -5,10 +5,9 @@ use serde::{ Serializer, }; +/// Serializable & deserializable `MIME` wrapper. #[derive(Clone, Debug, Eq, Hash, PartialEq, From)] -pub struct MimeWrapper { - pub mime: Mime, -} +pub struct MimeWrapper(pub Mime); impl Serialize for MimeWrapper { fn serialize( @@ -18,7 +17,7 @@ impl Serialize for MimeWrapper { where S: Serializer, { - serializer.serialize_str(self.mime.as_ref()) + serializer.serialize_str(self.0.as_ref()) } } @@ -38,7 +37,7 @@ impl<'a> Visitor<'a> for MimeVisitor { E: serde::de::Error, { match v.parse::() { - Ok(mime_type) => Ok(MimeWrapper { mime: mime_type }), + Ok(mime_type) => Ok(MimeWrapper(mime_type)), Err(e) => Err(E::custom(e)), } } diff --git a/src/types/parse_mode.rs b/src/types/parse_mode.rs index 93ad99ae..862c7c02 100644 --- a/src/types/parse_mode.rs +++ b/src/types/parse_mode.rs @@ -9,7 +9,8 @@ use std::{ use serde::{Deserialize, Serialize}; -/// ## Formatting options +/// Formatting options. +/// /// The Bot API supports basic formatting for messages. You can use bold, /// italic, underlined and strikethrough text, as well as inline links and /// pre-formatted code in your bots' messages. Telegram clients will render diff --git a/src/types/passport_file.rs b/src/types/passport_file.rs index 160e546e..18f1a5d4 100644 --- a/src/types/passport_file.rs +++ b/src/types/passport_file.rs @@ -1,8 +1,9 @@ use serde::{Deserialize, Serialize}; -/// This object represents a file uploaded to Telegram Passport. Currently all -/// Telegram Passport files are in JPEG format when decrypted and don't exceed -/// 10MB. +/// This object represents a file uploaded to Telegram Passport. +/// +/// Currently all Telegram Passport files are in JPEG format when decrypted and +/// don't exceed 10MB. /// /// [The official docs](https://core.telegram.org/bots/api#passportfile). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] diff --git a/src/types/reply_keyboard_markup.rs b/src/types/reply_keyboard_markup.rs index f2cd674e..59707764 100644 --- a/src/types/reply_keyboard_markup.rs +++ b/src/types/reply_keyboard_markup.rs @@ -10,7 +10,7 @@ use crate::types::KeyboardButton; /// [custom keyboard]: https://core.telegram.org/bots#keyboards /// [Introduction to bots]: https://core.telegram.org/bots#keyboards #[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Default)] pub struct ReplyKeyboardMarkup { /// Array of button rows, each represented by an Array of /// [`KeyboardButton`] objects @@ -43,3 +43,46 @@ pub struct ReplyKeyboardMarkup { /// [`Message`]: crate::types::Message pub selective: Option, } + +impl ReplyKeyboardMarkup { + pub fn append_row(mut self, buttons: Vec) -> Self { + self.keyboard.push(buttons); + self + } + + pub fn append_to_row( + mut self, + button: KeyboardButton, + index: usize, + ) -> Self { + match self.keyboard.get_mut(index) { + Some(buttons) => buttons.push(button), + None => self.keyboard.push(vec![button]), + }; + self + } + + pub fn resize_keyboard(mut self, val: T) -> Self + where + T: Into>, + { + self.resize_keyboard = val.into(); + self + } + + pub fn one_time_keyboard(mut self, val: T) -> Self + where + T: Into>, + { + self.one_time_keyboard = val.into(); + self + } + + pub fn selective(mut self, val: T) -> Self + where + T: Into>, + { + self.selective = val.into(); + self + } +} diff --git a/src/types/reply_keyboard_remove.rs b/src/types/reply_keyboard_remove.rs index 8537433d..35d55db7 100644 --- a/src/types/reply_keyboard_remove.rs +++ b/src/types/reply_keyboard_remove.rs @@ -3,10 +3,11 @@ use serde::{Deserialize, Serialize}; use crate::types::True; /// Upon receiving a message with this object, Telegram clients will remove the -/// current custom keyboard and display the default letter-keyboard. By default, -/// custom keyboards are displayed until a new keyboard is sent by a bot. An -/// exception is made for one-time keyboards that are hidden immediately after -/// the user presses a button (see [`ReplyKeyboardMarkup`]). +/// current custom keyboard and display the default letter-keyboard. +/// +/// By default, custom keyboards are displayed until a new keyboard is sent by a +/// bot. An exception is made for one-time keyboards that are hidden immediately +/// after the user presses a button (see [`ReplyKeyboardMarkup`]). /// /// [The official docs](https://core.telegram.org/bots/api#replykeyboardremove). /// diff --git a/src/types/reply_markup.rs b/src/types/reply_markup.rs index 57840d48..f69245e6 100644 --- a/src/types/reply_markup.rs +++ b/src/types/reply_markup.rs @@ -20,7 +20,7 @@ mod tests { #[test] fn inline_keyboard_markup() { - let data = InlineKeyboardMarkup::new(); + let data = InlineKeyboardMarkup::default(); let expected = ReplyMarkup::InlineKeyboardMarkup(data.clone()); let actual: ReplyMarkup = data.into(); assert_eq!(actual, expected) diff --git a/src/types/unit_false.rs b/src/types/unit_false.rs index 7821f313..058758f9 100644 --- a/src/types/unit_false.rs +++ b/src/types/unit_false.rs @@ -1,5 +1,6 @@ use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; +/// A type that is always false. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Default)] pub struct False; diff --git a/src/types/unit_true.rs b/src/types/unit_true.rs index 619cb7cc..cd71e5c2 100644 --- a/src/types/unit_true.rs +++ b/src/types/unit_true.rs @@ -3,6 +3,7 @@ use serde::{ ser::{Serialize, Serializer}, }; +/// A type that is always true. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Default)] pub struct True; diff --git a/src/types/update.rs b/src/types/update.rs index e3e18a0d..e0db02a3 100644 --- a/src/types/update.rs +++ b/src/types/update.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::types::{ CallbackQuery, Chat, ChosenInlineResult, InlineQuery, Message, Poll, - PollAnswer, PreCheckoutQuery, Sender, ShippingQuery, User, + PollAnswer, PreCheckoutQuery, ShippingQuery, User, }; /// This [object] represents an incoming update. @@ -80,14 +80,8 @@ pub enum UpdateKind { impl Update { pub fn user(&self) -> Option<&User> { match &self.kind { - UpdateKind::Message(m) => match m.from() { - Some(Sender::User(user)) => Some(user), - _ => None, - }, - UpdateKind::EditedMessage(m) => match m.from() { - Some(Sender::User(user)) => Some(user), - _ => None, - }, + UpdateKind::Message(m) => m.from(), + UpdateKind::EditedMessage(m) => m.from(), UpdateKind::CallbackQuery(query) => Some(&query.from), UpdateKind::ChosenInlineResult(chosen) => Some(&chosen.from), UpdateKind::InlineQuery(query) => Some(&query.from), @@ -114,7 +108,7 @@ impl Update { mod test { use crate::types::{ Chat, ChatKind, ForwardKind, LanguageCode, MediaKind, Message, - MessageKind, Sender, Update, UpdateKind, User, + MessageKind, Update, UpdateKind, User, }; // TODO: more tests for deserialization @@ -158,7 +152,7 @@ mod test { photo: None, }, kind: MessageKind::Common { - from: Sender::User(User { + from: Some(User { id: 218_485_655, is_bot: false, first_name: String::from("Waffle"), @@ -182,4 +176,33 @@ mod test { let actual = serde_json::from_str::(json).unwrap(); assert_eq!(expected, actual); } + + #[test] + fn de_private_chat_text_message() { + let text = r#" + { + "message": { + "chat": { + "first_name": "Hirrolot", + "id": 408258968, + "type": "private", + "username": "hirrolot" + }, + "date": 1581448857, + "from": { + "first_name": "Hirrolot", + "id": 408258968, + "is_bot": false, + "language_code": "en", + "username": "hirrolot" + }, + "message_id": 154, + "text": "4" + }, + "update_id": 306197398 + } +"#; + + assert!(serde_json::from_str::(text).is_ok()); + } } diff --git a/src/utils/command.rs b/src/utils/command.rs index 27969cac..7ca76b83 100644 --- a/src/utils/command.rs +++ b/src/utils/command.rs @@ -39,8 +39,12 @@ //! assert_eq!(args, vec!["3", "hours"]); //! ``` //! -//! [`parse_command`]: crate::utils::parse_command -//! [`parse_command_with_prefix`]: crate::utils::parse_command_with_prefix +//! See [examples/admin_bot] as a more complicated examples. +//! +//! [`parse_command`]: crate::utils::command::parse_command +//! [`parse_command_with_prefix`]: +//! crate::utils::command::parse_command_with_prefix +//! [examples/admin_bot]: https://github.com/teloxide/teloxide/blob/dev/examples/miltiple_handlers_bot/ pub use teloxide_macros::BotCommand; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 2d521013..15ff9ad4 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,5 @@ +//! Some useful utilities. + pub mod command; pub mod html; pub mod markdown; diff --git a/teloxide-macros/src/attr.rs b/teloxide-macros/src/attr.rs index 281baa37..f57c1783 100644 --- a/teloxide-macros/src/attr.rs +++ b/teloxide-macros/src/attr.rs @@ -1,11 +1,12 @@ -use syn::parse::{Parse, ParseStream}; -use syn::{LitStr, Token}; - +use syn::{ + parse::{Parse, ParseStream}, + LitStr, Token, +}; pub enum BotCommandAttribute { Prefix, Description, - RenameRule + RenameRule, } impl Parse for BotCommandAttribute { @@ -15,27 +16,23 @@ impl Parse for BotCommandAttribute { "prefix" => Ok(BotCommandAttribute::Prefix), "description" => Ok(BotCommandAttribute::Description), "rename" => Ok(BotCommandAttribute::RenameRule), - _ => Err(syn::Error::new(name_arg.span(), "unexpected argument")) + _ => Err(syn::Error::new(name_arg.span(), "unexpected argument")), } } } pub struct Attr { name: BotCommandAttribute, - value: String + value: String, } -impl Parse for Attr -{ +impl Parse for Attr { fn parse(input: ParseStream) -> Result { let name = input.parse::()?; input.parse::()?; let value = input.parse::()?.value(); - Ok(Self { - name, - value - }) + Ok(Self { name, value }) } } @@ -50,7 +47,7 @@ impl Attr { } pub struct VecAttrs { - pub data: Vec + pub data: Vec, } impl Parse for VecAttrs { @@ -62,8 +59,6 @@ impl Parse for VecAttrs { input.parse::()?; } } - Ok(Self { - data - }) + Ok(Self { data }) } } diff --git a/teloxide-macros/src/command.rs b/teloxide-macros/src/command.rs index c362faa4..69be6e58 100644 --- a/teloxide-macros/src/command.rs +++ b/teloxide-macros/src/command.rs @@ -1,5 +1,7 @@ -use crate::attr::{Attr, BotCommandAttribute}; -use crate::rename_rules::rename_by_rule; +use crate::{ + attr::{Attr, BotCommandAttribute}, + rename_rules::rename_by_rule, +}; pub struct Command { pub prefix: Option, @@ -33,7 +35,7 @@ impl Command { struct CommandAttrs { prefix: Option, description: Option, - rename: Option + rename: Option, } fn parse_attrs(attrs: &[Attr]) -> Result { @@ -44,16 +46,18 @@ fn parse_attrs(attrs: &[Attr]) -> Result { for attr in attrs { match attr.name() { BotCommandAttribute::Prefix => prefix = Some(attr.value()), - BotCommandAttribute::Description => description = Some(attr.value()), + BotCommandAttribute::Description => { + description = Some(attr.value()) + } BotCommandAttribute::RenameRule => rename_rule = Some(attr.value()), #[allow(unreachable_patterns)] - _ => return Err(format!("unexpected attribute")), + _ => return Err("unexpected attribute".to_owned()), } } Ok(CommandAttrs { prefix, description, - rename: rename_rule + rename: rename_rule, }) -} \ No newline at end of file +} diff --git a/teloxide-macros/src/enum_attributes.rs b/teloxide-macros/src/enum_attributes.rs index da290ca7..b54dc9a6 100644 --- a/teloxide-macros/src/enum_attributes.rs +++ b/teloxide-macros/src/enum_attributes.rs @@ -15,14 +15,14 @@ impl CommandEnum { let rename = attrs.rename; if let Some(rename_rule) = &rename { match rename_rule.as_str() { - "lowercase" => {}, - _ => return Err(format!("unallowed value")), + "lowercase" => {} + _ => return Err("disallowed value".to_owned()), } } Ok(Self { prefix, description, - rename_rule: rename + rename_rule: rename, }) } } @@ -30,7 +30,7 @@ impl CommandEnum { struct CommandAttrs { prefix: Option, description: Option, - rename: Option + rename: Option, } fn parse_attrs(attrs: &[Attr]) -> Result { @@ -41,16 +41,18 @@ fn parse_attrs(attrs: &[Attr]) -> Result { for attr in attrs { match attr.name() { BotCommandAttribute::Prefix => prefix = Some(attr.value()), - BotCommandAttribute::Description => description = Some(attr.value()), + BotCommandAttribute::Description => { + description = Some(attr.value()) + } BotCommandAttribute::RenameRule => rename_rule = Some(attr.value()), #[allow(unreachable_patterns)] - _ => return Err(format!("unexpected attribute")), + _ => return Err("unexpected attribute".to_owned()), } } Ok(CommandAttrs { prefix, description, - rename: rename_rule + rename: rename_rule, }) -} \ No newline at end of file +} diff --git a/teloxide-macros/src/lib.rs b/teloxide-macros/src/lib.rs index 16de50cf..b15f3492 100644 --- a/teloxide-macros/src/lib.rs +++ b/teloxide-macros/src/lib.rs @@ -5,13 +5,15 @@ mod rename_rules; extern crate proc_macro; extern crate syn; +use crate::{ + attr::{Attr, VecAttrs}, + command::Command, + enum_attributes::CommandEnum, + rename_rules::rename_by_rule, +}; use proc_macro::TokenStream; use quote::{quote, ToTokens}; -use syn::{DeriveInput, parse_macro_input}; -use crate::command::Command; -use crate::attr::{Attr, VecAttrs}; -use crate::enum_attributes::CommandEnum; -use crate::rename_rules::rename_by_rule; +use syn::{parse_macro_input, DeriveInput}; macro_rules! get_or_return { ($($some:tt)*) => { @@ -35,7 +37,8 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { Err(e) => return compile_error(e), }; - let variants: Vec<&syn::Variant> = data_enum.variants.iter().map(|attr| attr).collect(); + let variants: Vec<&syn::Variant> = + data_enum.variants.iter().map(|attr| attr).collect(); let mut variant_infos = vec![]; for variant in variants.iter() { @@ -44,10 +47,10 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { match attr.parse_args::() { Ok(mut attrs_) => { attrs.append(attrs_.data.as_mut()); - }, + } Err(e) => { return compile_error(e.to_compile_error()); - }, + } } } match Command::try_from(attrs.as_slice(), &variant.ident.to_string()) { @@ -60,35 +63,34 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { let variant_name = variant_infos.iter().map(|info| { if info.renamed { info.name.clone() - } - else if let Some(rename_rule) = &command_enum.rename_rule { + } else if let Some(rename_rule) = &command_enum.rename_rule { rename_by_rule(&info.name, rename_rule) - } - else { + } else { info.name.clone() } }); let variant_prefixes = variant_infos.iter().map(|info| { - if let Some(prefix) = &info.prefix { - prefix - } - else if let Some(prefix) = &command_enum.prefix { - prefix - } - else { - "/" - } + if let Some(prefix) = &info.prefix { + prefix + } else if let Some(prefix) = &command_enum.prefix { + prefix + } else { + "/" + } }); - let variant_str1 = variant_prefixes.zip(variant_name).map(|(prefix, command)| prefix.to_string() + command.as_str()); + let variant_str1 = variant_prefixes + .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_ref().map(String::as_str).unwrap_or("")); + let variant_description = variant_infos + .iter() + .map(|info| info.description.as_deref().unwrap_or("")); let ident = &input.ident; let global_description = if let Some(s) = &command_enum.description { quote! { #s, "\n", } - } - else { + } else { quote! {} }; @@ -114,27 +116,28 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { }; //for debug //println!("{}", &expanded.to_string()); - let tokens = TokenStream::from(expanded); - tokens + TokenStream::from(expanded) } fn get_enum_data(input: &DeriveInput) -> Result<&syn::DataEnum, TokenStream> { match &input.data { syn::Data::Enum(data) => Ok(data), - _ => Err(compile_error("TelegramBotCommand allowed only for enums")) + _ => Err(compile_error("TelegramBotCommand allowed only for enums")), } } -fn parse_attributes(input: &Vec) -> Result, TokenStream> { +fn parse_attributes( + input: &[syn::Attribute], +) -> Result, TokenStream> { let mut enum_attrs = Vec::new(); for attr in input.iter() { match attr.parse_args::() { Ok(mut attrs_) => { enum_attrs.append(attrs_.data.as_mut()); - }, + } Err(e) => { return Err(compile_error(e.to_compile_error())); - }, + } } } Ok(enum_attrs) @@ -142,7 +145,7 @@ fn parse_attributes(input: &Vec) -> Result, TokenStrea fn compile_error(data: T) -> TokenStream where - T: ToTokens + T: ToTokens, { TokenStream::from(quote! { compile_error!(#data) }) }