From c6e22c3f8a7583c87ff8d74c8bf754555376fc45 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 24 Feb 2020 14:48:37 +0600 Subject: [PATCH 001/755] Init --- .gitignore | 1 + Cargo.toml | 16 ++++ LICENSE | 22 ++++++ src/attr.rs | 64 ++++++++++++++++ src/command.rs | 54 ++++++++++++++ src/enum_attributes.rs | 50 +++++++++++++ src/lib.rs | 166 +++++++++++++++++++++++++++++++++++++++++ src/rename_rules.rs | 6 ++ 8 files changed, 379 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 src/attr.rs create mode 100644 src/command.rs create mode 100644 src/enum_attributes.rs create mode 100644 src/lib.rs create mode 100644 src/rename_rules.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2f7896d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..dc70f0e3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "teloxide-macros" +version = "0.1.2" +description = "The teloxide's macros for internal usage" +authors = ["p0lunin "] +license = "MIT" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +quote = "1.0.2" +syn = "1.0.13" + +[lib] +proc-macro = true diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..af7ef486 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ + +MIT License + +Copyright (c) 2019-2020 teloxide + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/attr.rs b/src/attr.rs new file mode 100644 index 00000000..f57c1783 --- /dev/null +++ b/src/attr.rs @@ -0,0 +1,64 @@ +use syn::{ + parse::{Parse, ParseStream}, + LitStr, Token, +}; + +pub enum BotCommandAttribute { + Prefix, + Description, + RenameRule, +} + +impl Parse for BotCommandAttribute { + fn parse(input: ParseStream) -> Result { + let name_arg: syn::Ident = input.parse()?; + match name_arg.to_string().as_str() { + "prefix" => Ok(BotCommandAttribute::Prefix), + "description" => Ok(BotCommandAttribute::Description), + "rename" => Ok(BotCommandAttribute::RenameRule), + _ => Err(syn::Error::new(name_arg.span(), "unexpected argument")), + } + } +} + +pub struct Attr { + name: BotCommandAttribute, + value: String, +} + +impl Parse for Attr { + fn parse(input: ParseStream) -> Result { + let name = input.parse::()?; + input.parse::()?; + let value = input.parse::()?.value(); + + Ok(Self { name, value }) + } +} + +impl Attr { + pub fn name(&self) -> &BotCommandAttribute { + &self.name + } + + pub fn value(&self) -> String { + self.value.clone() + } +} + +pub struct VecAttrs { + pub data: Vec, +} + +impl Parse for VecAttrs { + fn parse(input: ParseStream) -> Result { + let mut data = vec![]; + while !input.is_empty() { + data.push(input.parse()?); + if !input.is_empty() { + input.parse::()?; + } + } + Ok(Self { data }) + } +} diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 00000000..c3cc4609 --- /dev/null +++ b/src/command.rs @@ -0,0 +1,54 @@ +use crate::{ + attr::{Attr, BotCommandAttribute}, + rename_rules::rename_by_rule, +}; + +pub struct Command { + pub prefix: Option, + pub description: Option, + pub name: String, + pub renamed: bool, +} + +impl Command { + pub fn try_from(attrs: &[Attr], name: &str) -> Result { + let attrs = parse_attrs(attrs)?; + let mut new_name = name.to_string(); + let mut renamed = false; + + let prefix = attrs.prefix; + let description = attrs.description; + let rename = attrs.rename; + if let Some(rename_rule) = rename { + new_name = rename_by_rule(name, &rename_rule); + renamed = true; + } + Ok(Self { prefix, description, name: new_name, renamed }) + } +} + +struct CommandAttrs { + prefix: Option, + description: Option, + rename: Option, +} + +fn parse_attrs(attrs: &[Attr]) -> Result { + let mut prefix = None; + let mut description = None; + let mut rename_rule = None; + + for attr in attrs { + match attr.name() { + BotCommandAttribute::Prefix => prefix = Some(attr.value()), + BotCommandAttribute::Description => { + description = Some(attr.value()) + } + BotCommandAttribute::RenameRule => rename_rule = Some(attr.value()), + #[allow(unreachable_patterns)] + _ => return Err("unexpected attribute".to_owned()), + } + } + + Ok(CommandAttrs { prefix, description, rename: rename_rule }) +} diff --git a/src/enum_attributes.rs b/src/enum_attributes.rs new file mode 100644 index 00000000..7705e873 --- /dev/null +++ b/src/enum_attributes.rs @@ -0,0 +1,50 @@ +use crate::attr::{Attr, BotCommandAttribute}; + +pub struct CommandEnum { + pub prefix: Option, + pub description: Option, + pub rename_rule: Option, +} + +impl CommandEnum { + pub fn try_from(attrs: &[Attr]) -> Result { + let attrs = parse_attrs(attrs)?; + + let prefix = attrs.prefix; + let description = attrs.description; + let rename = attrs.rename; + if let Some(rename_rule) = &rename { + match rename_rule.as_str() { + "lowercase" => {} + _ => return Err("disallowed value".to_owned()), + } + } + Ok(Self { prefix, description, rename_rule: rename }) + } +} + +struct CommandAttrs { + prefix: Option, + description: Option, + rename: Option, +} + +fn parse_attrs(attrs: &[Attr]) -> Result { + let mut prefix = None; + let mut description = None; + let mut rename_rule = None; + + for attr in attrs { + match attr.name() { + BotCommandAttribute::Prefix => prefix = Some(attr.value()), + BotCommandAttribute::Description => { + description = Some(attr.value()) + } + BotCommandAttribute::RenameRule => rename_rule = Some(attr.value()), + #[allow(unreachable_patterns)] + _ => return Err("unexpected attribute".to_owned()), + } + } + + Ok(CommandAttrs { prefix, description, rename: rename_rule }) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..d8f5866e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,166 @@ +mod attr; +mod command; +mod enum_attributes; +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::{parse_macro_input, DeriveInput}; + +macro_rules! get_or_return { + ($($some:tt)*) => { + match $($some)* { + Ok(elem) => elem, + Err(e) => return e + }; + } +} + +#[proc_macro_derive(BotCommand, attributes(command))] +pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { + let input = parse_macro_input!(tokens as DeriveInput); + + let data_enum: &syn::DataEnum = get_or_return!(get_enum_data(&input)); + + let enum_attrs: Vec = get_or_return!(parse_attributes(&input.attrs)); + + let command_enum = match CommandEnum::try_from(enum_attrs.as_slice()) { + Ok(command_enum) => command_enum, + Err(e) => return compile_error(e), + }; + + let variants: Vec<&syn::Variant> = + data_enum.variants.iter().map(|attr| attr).collect(); + + let mut variant_infos = vec![]; + for variant in variants.iter() { + let mut attrs = Vec::new(); + for attr in &variant.attrs { + 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()) { + Ok(command) => variant_infos.push(command), + Err(e) => return compile_error(e), + } + } + + let variant_ident = variants.iter().map(|variant| &variant.ident); + let variant_name = variant_infos.iter().map(|info| { + if info.renamed { + info.name.clone() + } else if let Some(rename_rule) = &command_enum.rename_rule { + rename_by_rule(&info.name, rename_rule) + } 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 { + "/" + } + }); + 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_deref() + .map(|e| format!(" - {}", e)) + .unwrap_or(String::new()) + }); + + let ident = &input.ident; + + let global_description = if let Some(s) = &command_enum.description { + quote! { #s, "\n", } + } else { + quote! {} + }; + + let expanded = quote! { + impl BotCommand for #ident { + fn try_from(value: &str) -> Option { + match value { + #( + #variant_str1 => Some(Self::#variant_ident), + )* + _ => None + } + } + fn descriptions() -> String { + std::concat!(#global_description #(#variant_str2, #variant_description, '\n'),*).to_string() + } + fn parse(s: &str, bot_name: N) -> Option<(Self, Vec<&str>)> + where + N: Into + { + let mut words = s.split_whitespace(); + let mut splited = words.next()?.split('@'); + let command_raw = splited.next()?; + let bot = splited.next(); + let bot_name = bot_name.into(); + match bot { + Some(name) if name == bot_name => {} + None => {} + _ => return None, + } + let command = Self::try_from(command_raw)?; + Some((command, words.collect())) + } + } + }; + //for debug + //println!("{}", &expanded.to_string()); + 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")), + } +} + +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) +} + +fn compile_error(data: T) -> TokenStream +where + T: ToTokens, +{ + TokenStream::from(quote! { compile_error!(#data) }) +} diff --git a/src/rename_rules.rs b/src/rename_rules.rs new file mode 100644 index 00000000..ebba4eef --- /dev/null +++ b/src/rename_rules.rs @@ -0,0 +1,6 @@ +pub fn rename_by_rule(input: &str, rule: &str) -> String { + match rule { + "lowercase" => input.to_string().to_lowercase(), + _ => rule.to_string(), + } +} From 416e2c4c545743b91f356a2d783671b99396bce3 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 24 Feb 2020 14:49:39 +0600 Subject: [PATCH 002/755] Fix the description --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index dc70f0e3..f59d6c6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "teloxide-macros" version = "0.1.2" -description = "The teloxide's macros for internal usage" +description = "The teloxide's procedural macros" authors = ["p0lunin "] license = "MIT" edition = "2018" From 07c3897f02524113766ae52a0b184ff6effadc12 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 24 Feb 2020 14:54:06 +0600 Subject: [PATCH 003/755] Create rust.yml --- .github/workflows/rust.yml | 72 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 .github/workflows/rust.yml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 00000000..0ec2a488 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,72 @@ +on: [push, pull_request] + +name: Continuous integration + +jobs: + ci: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + - beta + - nightly + + steps: + - uses: actions/checkout@v1 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + components: rustfmt, clippy + + - name: stable/beta build + uses: actions-rs/cargo@v1 + if: matrix.rust == 'stable' || matrix.rust == 'beta' + with: + command: build + args: --verbose --features "" + + - name: nightly build + uses: actions-rs/cargo@v1 + if: matrix.rust == 'nightly' + with: + command: build + args: --verbose --all-features + + - name: stable/beta test + uses: actions-rs/cargo@v1 + if: matrix.rust == 'stable' || matrix.rust == 'beta' + with: + command: test + args: --verbose --features "" + + - name: nightly test + uses: actions-rs/cargo@v1 + if: matrix.rust == 'nightly' + with: + command: test + args: --verbose --all-features + + - name: fmt + uses: actions-rs/cargo@v1 + if: matrix.rust == 'nightly' + with: + command: fmt + args: --all -- --check + + - name: stable/beta clippy + uses: actions-rs/cargo@v1 + if: matrix.rust == 'stable' || matrix.rust == 'beta' + with: + command: clippy + args: --all-targets --features "" -- -D warnings + + - name: nightly clippy + uses: actions-rs/cargo@v1 + if: matrix.rust == 'nightly' + with: + command: clippy + args: --all-targets --all-features -- -D warnings From 65d7087e40608eceac459b5a22d46ad9f956581e Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 24 Feb 2020 15:12:33 +0600 Subject: [PATCH 004/755] Create CHANGELOG.md --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..29db8040 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,34 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +### Changed + - The description in `Cargo.toml` was changed to from "The teloxide's macros for internal usage" to "The teloxide's procedural macros". + +### Added + - This `CHANGELOG.md`. + + +## [0.1.2] - 2020-02-24 +### Changed + - The same as v0.1.1, but fixes [the issue](https://github.com/teloxide/teloxide/issues/176) about backwards compatibility. + + +## [0.2.0] - [YANKED] +### Changed + - Fixes [the issue](https://github.com/teloxide/teloxide/issues/176) about backwards compatibility, but fairly soon I realised that semver recommends to use v0.1.2 instead. + + +## [0.1.1] - 2020-02-23 +### Added + - The `LICENSE` file. +### Changed + - Backwards compatibility is broken and was fixed in v0.1.2. + + +## [0.1.0] - 2020-02-19 +### Added + - This project. From 8d4116fcd8aab5fe6a48e105022a3ae49bafd614 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 24 Feb 2020 15:13:18 +0600 Subject: [PATCH 005/755] Fix fmtcheck --- .gitignore | 1 + src/command.rs | 17 ++++++++++++----- src/enum_attributes.rs | 16 +++++++++++----- src/lib.rs | 7 ++----- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 2f7896d1..2c96eb1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ target/ +Cargo.lock diff --git a/src/command.rs b/src/command.rs index c3cc4609..921153ee 100644 --- a/src/command.rs +++ b/src/command.rs @@ -23,7 +23,12 @@ impl Command { new_name = rename_by_rule(name, &rename_rule); renamed = true; } - Ok(Self { prefix, description, name: new_name, renamed }) + Ok(Self { + prefix, + description, + name: new_name, + renamed, + }) } } @@ -41,14 +46,16 @@ 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("unexpected attribute".to_owned()), } } - Ok(CommandAttrs { prefix, description, rename: rename_rule }) + Ok(CommandAttrs { + prefix, + description, + rename: rename_rule, + }) } diff --git a/src/enum_attributes.rs b/src/enum_attributes.rs index 7705e873..728b80eb 100644 --- a/src/enum_attributes.rs +++ b/src/enum_attributes.rs @@ -19,7 +19,11 @@ impl CommandEnum { _ => return Err("disallowed value".to_owned()), } } - Ok(Self { prefix, description, rename_rule: rename }) + Ok(Self { + prefix, + description, + rename_rule: rename, + }) } } @@ -37,14 +41,16 @@ 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("unexpected attribute".to_owned()), } } - Ok(CommandAttrs { prefix, description, rename: rename_rule }) + Ok(CommandAttrs { + prefix, + description, + rename: rename_rule, + }) } diff --git a/src/lib.rs b/src/lib.rs index d8f5866e..6cf20baa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,8 +37,7 @@ 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() { @@ -141,9 +140,7 @@ fn get_enum_data(input: &DeriveInput) -> Result<&syn::DataEnum, TokenStream> { } } -fn parse_attributes( - input: &[syn::Attribute], -) -> 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::() { From 0b3e2994b20d48ad176f587e9ebb337403f5a36d Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 24 Feb 2020 15:13:53 +0600 Subject: [PATCH 006/755] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29db8040..1f2f2f6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - This `CHANGELOG.md`. + - `.gitignore`. ## [0.1.2] - 2020-02-24 From 5abaeb2c003e21af70dfb1120820feb89f8764e0 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 24 Feb 2020 15:19:03 +0600 Subject: [PATCH 007/755] Fix Clippy --- .gitignore | 4 +++- src/lib.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 2c96eb1b..f940c081 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ -target/ +target Cargo.lock +.idea +.vscode diff --git a/src/lib.rs b/src/lib.rs index 6cf20baa..fa3a408b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,7 +85,7 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { info.description .as_deref() .map(|e| format!(" - {}", e)) - .unwrap_or(String::new()) + .unwrap_or_default() }); let ident = &input.ident; From c238906b69fd33d6856f57c4f231e1e8cbf16897 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Mon, 24 Feb 2020 17:37:17 +0200 Subject: [PATCH 008/755] added impl_try_from function --- src/command.rs | 12 ++++++++++++ src/lib.rs | 30 ++++++++++++++++++++---------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/command.rs b/src/command.rs index 921153ee..36496f26 100644 --- a/src/command.rs +++ b/src/command.rs @@ -2,6 +2,7 @@ use crate::{ attr::{Attr, BotCommandAttribute}, rename_rules::rename_by_rule, }; +use crate::enum_attributes::CommandEnum; pub struct Command { pub prefix: Option, @@ -30,6 +31,17 @@ impl Command { renamed, }) } + + pub fn get_matched_value(&self, global_parameters: &CommandEnum) -> String { + let prefix = if let Some(prefix) = &self.prefix { + prefix + } else if let Some(prefix) = &global_parameters.prefix { + prefix + } else { + "/" + }; + String::from(prefix) + &self.name + } } struct CommandAttrs { diff --git a/src/lib.rs b/src/lib.rs index fa3a408b..cf532aa0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ use crate::{ }; use proc_macro::TokenStream; use quote::{quote, ToTokens}; -use syn::{parse_macro_input, DeriveInput}; +use syn::{parse_macro_input, DeriveInput, Variant}; macro_rules! get_or_return { ($($some:tt)*) => { @@ -58,7 +58,6 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { } } - let variant_ident = variants.iter().map(|variant| &variant.ident); let variant_name = variant_infos.iter().map(|info| { if info.renamed { info.name.clone() @@ -96,16 +95,11 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { quote! {} }; + let fn_try_from = impl_try_parse_command(&variants, &variant_infos, &command_enum); + let expanded = quote! { impl BotCommand for #ident { - fn try_from(value: &str) -> Option { - match value { - #( - #variant_str1 => Some(Self::#variant_ident), - )* - _ => None - } - } + #fn_try_from fn descriptions() -> String { std::concat!(#global_description #(#variant_str2, #variant_description, '\n'),*).to_string() } @@ -133,6 +127,22 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { TokenStream::from(expanded) } +fn impl_try_parse_command(variants: &[&Variant], infos: &[Command], global: &CommandEnum) -> impl ToTokens { + let matching_values = infos.iter().map(|c| c.get_matched_value(global)); + let variant_ident = variants.iter().map(|variant| &variant.ident); + + quote! { + fn try_from(value: &str) -> Option { + match value { + #( + #matching_values => Some(Self::#variant_ident), + )* + _ => None + } + } + } +} + fn get_enum_data(input: &DeriveInput) -> Result<&syn::DataEnum, TokenStream> { match &input.data { syn::Data::Enum(data) => Ok(data), From 9dc6aab3db7b112c0c04c18c9f82451dc5e1cc91 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Mon, 24 Feb 2020 17:43:36 +0200 Subject: [PATCH 009/755] added impl_descriptions function --- src/lib.rs | 62 ++++++++++++++++++++---------------------------------- 1 file changed, 23 insertions(+), 39 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cf532aa0..186ca75f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,6 @@ use crate::{ attr::{Attr, VecAttrs}, command::Command, enum_attributes::CommandEnum, - rename_rules::rename_by_rule, }; use proc_macro::TokenStream; use quote::{quote, ToTokens}; @@ -58,51 +57,15 @@ 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 { - rename_by_rule(&info.name, rename_rule) - } 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 { - "/" - } - }); - 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_deref() - .map(|e| format!(" - {}", e)) - .unwrap_or_default() - }); - let ident = &input.ident; - let global_description = if let Some(s) = &command_enum.description { - quote! { #s, "\n", } - } else { - quote! {} - }; - let fn_try_from = impl_try_parse_command(&variants, &variant_infos, &command_enum); + let fn_descriptions = impl_descriptions(&variant_infos, &command_enum); let expanded = quote! { impl BotCommand for #ident { #fn_try_from - fn descriptions() -> String { - std::concat!(#global_description #(#variant_str2, #variant_description, '\n'),*).to_string() - } + #fn_descriptions fn parse(s: &str, bot_name: N) -> Option<(Self, Vec<&str>)> where N: Into @@ -143,6 +106,27 @@ fn impl_try_parse_command(variants: &[&Variant], infos: &[Command], global: &Com } } +fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> impl ToTokens { + let global_description = if let Some(s) = &global.description { + quote! { #s, "\n", } + } else { + quote! {} + }; + let command = infos.iter().map(|c| c.get_matched_value(global)); + let description = infos.iter().map(|info| { + info.description + .as_deref() + .map(|e| format!(" - {}", e)) + .unwrap_or_default() + }); + + quote! { + fn descriptions() -> &'static str { + std::concat!(#global_description #(#command, #description, '\n'),*) + } + } +} + fn get_enum_data(input: &DeriveInput) -> Result<&syn::DataEnum, TokenStream> { match &input.data { syn::Data::Enum(data) => Ok(data), From fdf68463d959405704be66ebf62c83fe9fea0c17 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Mon, 24 Feb 2020 17:55:46 +0200 Subject: [PATCH 010/755] added impl_parse function --- src/lib.rs | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 186ca75f..4e445232 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,33 +61,17 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { let fn_try_from = impl_try_parse_command(&variants, &variant_infos, &command_enum); let fn_descriptions = impl_descriptions(&variant_infos, &command_enum); + let fn_parse = impl_parse(); - let expanded = quote! { + let trait_impl = quote! { impl BotCommand for #ident { #fn_try_from #fn_descriptions - fn parse(s: &str, bot_name: N) -> Option<(Self, Vec<&str>)> - where - N: Into - { - let mut words = s.split_whitespace(); - let mut splited = words.next()?.split('@'); - let command_raw = splited.next()?; - let bot = splited.next(); - let bot_name = bot_name.into(); - match bot { - Some(name) if name == bot_name => {} - None => {} - _ => return None, - } - let command = Self::try_from(command_raw)?; - Some((command, words.collect())) - } + #fn_parse } }; - //for debug - //println!("{}", &expanded.to_string()); - TokenStream::from(expanded) + + TokenStream::from(trait_impl) } fn impl_try_parse_command(variants: &[&Variant], infos: &[Command], global: &CommandEnum) -> impl ToTokens { @@ -127,6 +111,28 @@ fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> impl ToTokens { } } +fn impl_parse() -> impl ToTokens { + quote! { + fn parse(s: &str, bot_name: N) -> Option<(Self, Vec<&str>)> + where + N: Into + { + let mut words = s.split_whitespace(); + let mut splited = words.next()?.split('@'); + let command_raw = splited.next()?; + let bot = splited.next(); + let bot_name = bot_name.into(); + match bot { + Some(name) if name == bot_name => {} + None => {} + _ => return None, + } + let command = Self::try_from(command_raw)?; + Some((command, words.collect())) + } + } +} + fn get_enum_data(input: &DeriveInput) -> Result<&syn::DataEnum, TokenStream> { match &input.data { syn::Data::Enum(data) => Ok(data), From 161300c739d25fe0bc6759fead397a099e1b5130 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 25 Feb 2020 02:26:30 +0600 Subject: [PATCH 011/755] Update CHANGELOG.md --- CHANGELOG.md | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f2f2f6a..be5f19e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,28 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Changed - The description in `Cargo.toml` was changed to from "The teloxide's macros for internal usage" to "The teloxide's procedural macros". + - Added the functionality to parse commands only with a correct bot's name. ### Added - This `CHANGELOG.md`. - `.gitignore`. -## [0.1.2] - 2020-02-24 -### Changed - - The same as v0.1.1, but fixes [the issue](https://github.com/teloxide/teloxide/issues/176) about backwards compatibility. +## [0.1.2] - [YANKED] +Invalid. ## [0.2.0] - [YANKED] -### Changed - - Fixes [the issue](https://github.com/teloxide/teloxide/issues/176) about backwards compatibility, but fairly soon I realised that semver recommends to use v0.1.2 instead. +Invalid. -## [0.1.1] - 2020-02-23 -### Added - - The `LICENSE` file. -### Changed - - Backwards compatibility is broken and was fixed in v0.1.2. - +## [0.1.1] - [YANKED] +Invalid. ## [0.1.0] - 2020-02-19 ### Added From 2d12e36a5c4c657c6b601b07b958fb895869baa5 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 25 Feb 2020 02:33:10 +0600 Subject: [PATCH 012/755] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be5f19e2..f4ea13fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Changed - The description in `Cargo.toml` was changed to from "The teloxide's macros for internal usage" to "The teloxide's procedural macros". - - Added the functionality to parse commands only with a correct bot's name. ### Added - This `CHANGELOG.md`. - `.gitignore`. + - The functionality to parse commands only with a correct bot's name (breaks backwards compatibility). ## [0.1.2] - [YANKED] From 215c1d502c26cfcd17db01f7c56896f3b74bf7e5 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 25 Feb 2020 03:20:21 +0600 Subject: [PATCH 013/755] Prepare to v0.2.1 --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4ea13fb..23c1e93f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.2.1] - 2020-02-25 ### Changed - The description in `Cargo.toml` was changed to from "The teloxide's macros for internal usage" to "The teloxide's procedural macros". diff --git a/Cargo.toml b/Cargo.toml index f59d6c6c..2c5f5a64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teloxide-macros" -version = "0.1.2" +version = "0.2.1" description = "The teloxide's procedural macros" authors = ["p0lunin "] license = "MIT" From eea651a8a27bf538e23339b44127f77c55aecc6d Mon Sep 17 00:00:00 2001 From: p0lunin Date: Tue, 25 Feb 2020 12:09:58 +0200 Subject: [PATCH 014/755] added Readme.md --- Readme.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Readme.md diff --git a/Readme.md b/Readme.md new file mode 100644 index 00000000..04e9e074 --- /dev/null +++ b/Readme.md @@ -0,0 +1,39 @@ +# teloxide-macros +The teloxide's procedural macros. + +## Example +```rust +use teloxide::utils::command::BotCommand; +#[derive(BotCommand, PartialEq, Debug)] +#[command(rename = "lowercase")] +enum AdminCommand { + Mute, + Ban, +} +let (command, args) = AdminCommand::parse("/ban 5 h", "bot_name").unwrap(); +assert_eq!(command, AdminCommand::Ban); +assert_eq!(args, vec!["5", "h"]); +``` +## Enum attributes + 1. `#[command(rename = "rule")]` +Rename all commands by rule. Allowed rules are `lowercase`. If you will not +use this attribute, commands will be parsed by their original names. + + 2. `#[command(prefix = "prefix")]` +Change a prefix for all commands (the default is `/`). + + 3. `#[command(description = "description")]` +Add a sumary description of commands before all commands. + +## Variant attributes + 1. `#[command(rename = "rule")]` +Rename one command by a rule. Allowed rules are `lowercase`, `%some_name%`, +where `%some_name%` is any string, a new name. + + 2. `#[command(prefix = "prefix")]` +Change a prefix for one command (the default is `/`). + + 3. `#[command(description = "description")]` +Add a description of one command. + +All variant attributes overlap the `enum` attributes. \ No newline at end of file From db4b7b2d3cb68e37a130fc77141367f93920dc40 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Wed, 4 Mar 2020 18:29:23 +0200 Subject: [PATCH 015/755] added parsing unnamed fields --- src/command.rs | 2 +- src/fields_parse.rs | 12 +++++++ src/lib.rs | 87 +++++++++++++++++++++++++-------------------- 3 files changed, 61 insertions(+), 40 deletions(-) create mode 100644 src/fields_parse.rs diff --git a/src/command.rs b/src/command.rs index 36496f26..244b34ac 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,8 +1,8 @@ +use crate::enum_attributes::CommandEnum; use crate::{ attr::{Attr, BotCommandAttribute}, rename_rules::rename_by_rule, }; -use crate::enum_attributes::CommandEnum; pub struct Command { pub prefix: Option, diff --git a/src/fields_parse.rs b/src/fields_parse.rs new file mode 100644 index 00000000..6c959a22 --- /dev/null +++ b/src/fields_parse.rs @@ -0,0 +1,12 @@ +extern crate quote; + +use quote::quote; +use quote::ToTokens; +use syn::FieldsUnnamed; + +pub fn impl_parse_args_unnamed(data: &FieldsUnnamed) -> quote::__rt::TokenStream { + let iter = 0..data.unnamed.len(); + quote! { + (#(FromStr::from_str(args.get(#iter)),)*) + } +} diff --git a/src/lib.rs b/src/lib.rs index 4e445232..5bbc251f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,13 @@ mod attr; mod command; mod enum_attributes; +mod fields_parse; mod rename_rules; extern crate proc_macro; +extern crate quote; extern crate syn; +use crate::fields_parse::impl_parse_args_unnamed; use crate::{ attr::{Attr, VecAttrs}, command::Command, @@ -12,7 +15,7 @@ use crate::{ }; use proc_macro::TokenStream; use quote::{quote, ToTokens}; -use syn::{parse_macro_input, DeriveInput, Variant}; +use syn::{parse_macro_input, DeriveInput, Fields, Variant}; macro_rules! get_or_return { ($($some:tt)*) => { @@ -36,10 +39,20 @@ 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(|variant| variant).collect(); + + let mut vec_impl_create = vec![]; + for variant in &variants { + match &variant.fields { + Fields::Unnamed(fields) => { + vec_impl_create.push(impl_parse_args_unnamed(fields)); + } + _ => panic!("only unnamed fields"), // TODO: named fields + } + } let mut variant_infos = vec![]; - for variant in variants.iter() { + for variant in &variants { let mut attrs = Vec::new(); for attr in &variant.attrs { match attr.parse_args::() { @@ -59,13 +72,11 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { let ident = &input.ident; - let fn_try_from = impl_try_parse_command(&variants, &variant_infos, &command_enum); let fn_descriptions = impl_descriptions(&variant_infos, &command_enum); - let fn_parse = impl_parse(); + let fn_parse = impl_parse(&variants, &variant_infos, &command_enum, &vec_impl_create); let trait_impl = quote! { impl BotCommand for #ident { - #fn_try_from #fn_descriptions #fn_parse } @@ -74,23 +85,7 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { TokenStream::from(trait_impl) } -fn impl_try_parse_command(variants: &[&Variant], infos: &[Command], global: &CommandEnum) -> impl ToTokens { - let matching_values = infos.iter().map(|c| c.get_matched_value(global)); - let variant_ident = variants.iter().map(|variant| &variant.ident); - - quote! { - fn try_from(value: &str) -> Option { - match value { - #( - #matching_values => Some(Self::#variant_ident), - )* - _ => None - } - } - } -} - -fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> impl ToTokens { +fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> quote::__rt::TokenStream { let global_description = if let Some(s) = &global.description { quote! { #s, "\n", } } else { @@ -111,25 +106,39 @@ fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> impl ToTokens { } } -fn impl_parse() -> impl ToTokens { +fn impl_parse( + variants: &[&Variant], + infos: &[Command], + global: &CommandEnum, + variants_initialization: &[quote::__rt::TokenStream], +) -> quote::__rt::TokenStream { + let matching_values = infos.iter().map(|c| c.get_matched_value(global)); + let variant_ident = variants.iter().map(|variant| &variant.ident); + quote! { - fn parse(s: &str, bot_name: N) -> Option<(Self, Vec<&str>)> + fn parse(s: &str, bot_name: N) -> Option where - N: Into + N: Into { - let mut words = s.split_whitespace(); - let mut splited = words.next()?.split('@'); - let command_raw = splited.next()?; - let bot = splited.next(); - let bot_name = bot_name.into(); - match bot { - Some(name) if name == bot_name => {} - None => {} - _ => return None, - } - let command = Self::try_from(command_raw)?; - Some((command, words.collect())) - } + let mut words = s.split_whitespace(); + let mut splited = words.next()?.split('@'); + let command_raw = splited.next()?; + let bot = splited.next(); + let bot_name = bot_name.into(); + match bot { + Some(name) if name == bot_name => {} + None => {} + _ => return None, + } + let args: Vec<&str> = words.collect(); + match command { + #( + #matching_values => Some(Self::#variant_ident #variants_initialization), + )* + _ => return None, + } + Some((command, words.collect())) + } } } From 3c99d379efecb9bb6efa77923dd201b6630598c3 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Wed, 4 Mar 2020 18:33:54 +0200 Subject: [PATCH 016/755] added parsing unit variant --- src/fields_parse.rs | 1 - src/lib.rs | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/fields_parse.rs b/src/fields_parse.rs index 6c959a22..6abff534 100644 --- a/src/fields_parse.rs +++ b/src/fields_parse.rs @@ -1,7 +1,6 @@ extern crate quote; use quote::quote; -use quote::ToTokens; use syn::FieldsUnnamed; pub fn impl_parse_args_unnamed(data: &FieldsUnnamed) -> quote::__rt::TokenStream { diff --git a/src/lib.rs b/src/lib.rs index 5bbc251f..61ad2f63 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,6 +47,9 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { Fields::Unnamed(fields) => { vec_impl_create.push(impl_parse_args_unnamed(fields)); } + Fields::Unit => { + vec_impl_create.push( quote! {}); + } _ => panic!("only unnamed fields"), // TODO: named fields } } From 5afd65226cdc76c5bf86f24ad4f3696432265a9d Mon Sep 17 00:00:00 2001 From: p0lunin Date: Wed, 4 Mar 2020 18:38:08 +0200 Subject: [PATCH 017/755] fix command --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 61ad2f63..0a10d320 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -134,7 +134,7 @@ fn impl_parse( _ => return None, } let args: Vec<&str> = words.collect(); - match command { + match command_raw { #( #matching_values => Some(Self::#variant_ident #variants_initialization), )* From 801ee3a9c93e160da03c2c7475409a6f8f24759b Mon Sep 17 00:00:00 2001 From: p0lunin Date: Wed, 4 Mar 2020 18:40:02 +0200 Subject: [PATCH 018/755] fix return --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0a10d320..c525117e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -138,9 +138,8 @@ fn impl_parse( #( #matching_values => Some(Self::#variant_ident #variants_initialization), )* - _ => return None, + _ => None, } - Some((command, words.collect())) } } } From 0ef5a59a1ab588a75e1cb921d2fa118bb79770f7 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 25 Apr 2020 19:57:45 +0300 Subject: [PATCH 019/755] fixes and added type safe arguments --- src/command.rs | 7 ++++++- src/enum_attributes.rs | 1 + src/fields_parse.rs | 8 +++++--- src/lib.rs | 8 ++++---- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/command.rs b/src/command.rs index 244b34ac..0c8bea10 100644 --- a/src/command.rs +++ b/src/command.rs @@ -40,7 +40,12 @@ impl Command { } else { "/" }; - String::from(prefix) + &self.name + if let Some(rule) = &global_parameters.rename_rule { + String::from(prefix) + &rename_by_rule(&self.name, rule.as_str()) + } + else { + String::from(prefix) + &self.name + } } } diff --git a/src/enum_attributes.rs b/src/enum_attributes.rs index 728b80eb..1ca59636 100644 --- a/src/enum_attributes.rs +++ b/src/enum_attributes.rs @@ -1,5 +1,6 @@ use crate::attr::{Attr, BotCommandAttribute}; +#[derive(Debug)] pub struct CommandEnum { pub prefix: Option, pub description: Option, diff --git a/src/fields_parse.rs b/src/fields_parse.rs index 6abff534..2eb1998d 100644 --- a/src/fields_parse.rs +++ b/src/fields_parse.rs @@ -1,11 +1,13 @@ extern crate quote; -use quote::quote; +use quote::{quote}; use syn::FieldsUnnamed; pub fn impl_parse_args_unnamed(data: &FieldsUnnamed) -> quote::__rt::TokenStream { let iter = 0..data.unnamed.len(); - quote! { - (#(FromStr::from_str(args.get(#iter)),)*) + let mut tokens = quote! {}; + for _ in iter { + tokens.extend(quote! { CommandArgument::parse(&mut args)?, }); } + quote! { (#tokens) } } diff --git a/src/lib.rs b/src/lib.rs index c525117e..01d6bc8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,8 +103,8 @@ fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> quote::__rt::To }); quote! { - fn descriptions() -> &'static str { - std::concat!(#global_description #(#command, #description, '\n'),*) + fn descriptions() -> String { + std::concat!(#global_description #(#command, #description, '\n'),*).to_string() } } } @@ -123,7 +123,7 @@ fn impl_parse( where N: Into { - let mut words = s.split_whitespace(); + let mut words = s.splitn(2, ' '); let mut splited = words.next()?.split('@'); let command_raw = splited.next()?; let bot = splited.next(); @@ -133,7 +133,7 @@ fn impl_parse( None => {} _ => return None, } - let args: Vec<&str> = words.collect(); + let mut args = words.next().unwrap_or("").to_string(); match command_raw { #( #matching_values => Some(Self::#variant_ident #variants_initialization), From fd2665640885031fc11da8b33b7bee7ecb424a49 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 25 Apr 2020 20:02:47 +0300 Subject: [PATCH 020/755] fix dependency --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f59d6c6c..2a98ddd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -quote = "1.0.2" +quote = "=1.0.2" syn = "1.0.13" [lib] From 7f7b783da86de49a4d0cd379b84079dcb19d4277 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 25 Apr 2020 20:09:02 +0300 Subject: [PATCH 021/755] fixes quote version --- Cargo.toml | 2 +- src/fields_parse.rs | 2 +- src/lib.rs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2a98ddd6..35f8e366 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -quote = "=1.0.2" +quote = "1.0.3" syn = "1.0.13" [lib] diff --git a/src/fields_parse.rs b/src/fields_parse.rs index 2eb1998d..39644477 100644 --- a/src/fields_parse.rs +++ b/src/fields_parse.rs @@ -3,7 +3,7 @@ extern crate quote; use quote::{quote}; use syn::FieldsUnnamed; -pub fn impl_parse_args_unnamed(data: &FieldsUnnamed) -> quote::__rt::TokenStream { +pub fn impl_parse_args_unnamed(data: &FieldsUnnamed) -> quote::__private::TokenStream { let iter = 0..data.unnamed.len(); let mut tokens = quote! {}; for _ in iter { diff --git a/src/lib.rs b/src/lib.rs index 01d6bc8e..74b23d6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,7 +88,7 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { TokenStream::from(trait_impl) } -fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> quote::__rt::TokenStream { +fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> quote::__private::TokenStream { let global_description = if let Some(s) = &global.description { quote! { #s, "\n", } } else { @@ -113,8 +113,8 @@ fn impl_parse( variants: &[&Variant], infos: &[Command], global: &CommandEnum, - variants_initialization: &[quote::__rt::TokenStream], -) -> quote::__rt::TokenStream { + variants_initialization: &[quote::__private::TokenStream], +) -> quote::__private::TokenStream { let matching_values = infos.iter().map(|c| c.get_matched_value(global)); let variant_ident = variants.iter().map(|variant| &variant.ident); From af569e83560555d9e7e68e9a87e593dec50e8f2a Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 25 Apr 2020 20:10:24 +0300 Subject: [PATCH 022/755] fmt --- src/command.rs | 3 +-- src/fields_parse.rs | 2 +- src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/command.rs b/src/command.rs index 0c8bea10..047239c4 100644 --- a/src/command.rs +++ b/src/command.rs @@ -42,8 +42,7 @@ impl Command { }; if let Some(rule) = &global_parameters.rename_rule { String::from(prefix) + &rename_by_rule(&self.name, rule.as_str()) - } - else { + } else { String::from(prefix) + &self.name } } diff --git a/src/fields_parse.rs b/src/fields_parse.rs index 39644477..af4a7f5f 100644 --- a/src/fields_parse.rs +++ b/src/fields_parse.rs @@ -1,6 +1,6 @@ extern crate quote; -use quote::{quote}; +use quote::quote; use syn::FieldsUnnamed; pub fn impl_parse_args_unnamed(data: &FieldsUnnamed) -> quote::__private::TokenStream { diff --git a/src/lib.rs b/src/lib.rs index 74b23d6d..7c1c919c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,7 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { vec_impl_create.push(impl_parse_args_unnamed(fields)); } Fields::Unit => { - vec_impl_create.push( quote! {}); + vec_impl_create.push(quote! {}); } _ => panic!("only unnamed fields"), // TODO: named fields } From d27e741539f8980a7f5c2d7fb6154765d25ac4f1 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 30 May 2020 21:22:11 +0300 Subject: [PATCH 023/755] added typed commands for unnamed variants of enum --- src/attr.rs | 4 ++ src/command.rs | 26 ++++++++---- src/enum_attributes.rs | 40 +++++------------- src/fields_parse.rs | 77 +++++++++++++++++++++++++++++++++++ src/lib.rs | 92 ++++++++++++++++++++++++------------------ 5 files changed, 162 insertions(+), 77 deletions(-) create mode 100644 src/fields_parse.rs diff --git a/src/attr.rs b/src/attr.rs index f57c1783..949e1659 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -7,6 +7,8 @@ pub enum BotCommandAttribute { Prefix, Description, RenameRule, + CustomParser, + Separator, } impl Parse for BotCommandAttribute { @@ -16,6 +18,8 @@ impl Parse for BotCommandAttribute { "prefix" => Ok(BotCommandAttribute::Prefix), "description" => Ok(BotCommandAttribute::Description), "rename" => Ok(BotCommandAttribute::RenameRule), + "parse_with" => Ok(BotCommandAttribute::CustomParser), + "separator" => Ok(BotCommandAttribute::Separator), _ => Err(syn::Error::new(name_arg.span(), "unexpected argument")), } } diff --git a/src/command.rs b/src/command.rs index 36496f26..0aa11fb9 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,12 +1,14 @@ +use crate::enum_attributes::CommandEnum; +use crate::fields_parse::ParserType; use crate::{ attr::{Attr, BotCommandAttribute}, rename_rules::rename_by_rule, }; -use crate::enum_attributes::CommandEnum; pub struct Command { pub prefix: Option, pub description: Option, + pub parser: Option, pub name: String, pub renamed: bool, } @@ -20,6 +22,7 @@ impl Command { let prefix = attrs.prefix; let description = attrs.description; let rename = attrs.rename; + let parser = attrs.parser; if let Some(rename_rule) = rename { new_name = rename_by_rule(name, &rename_rule); renamed = true; @@ -27,6 +30,7 @@ impl Command { Ok(Self { prefix, description, + parser, name: new_name, renamed, }) @@ -44,24 +48,28 @@ impl Command { } } -struct CommandAttrs { - prefix: Option, - description: Option, - rename: Option, +pub struct CommandAttrs { + pub(crate) prefix: Option, + pub(crate) description: Option, + pub(crate) rename: Option, + pub(crate) parser: Option, + pub(crate) separator: Option, } -fn parse_attrs(attrs: &[Attr]) -> Result { +pub fn parse_attrs(attrs: &[Attr]) -> Result { let mut prefix = None; let mut description = None; let mut rename_rule = None; + let mut parser = None; + let mut separator = None; for attr in attrs { match attr.name() { BotCommandAttribute::Prefix => prefix = Some(attr.value()), BotCommandAttribute::Description => description = Some(attr.value()), BotCommandAttribute::RenameRule => rename_rule = Some(attr.value()), - #[allow(unreachable_patterns)] - _ => return Err("unexpected attribute".to_owned()), + BotCommandAttribute::CustomParser => parser = Some(ParserType::parse(&attr.value())), + BotCommandAttribute::Separator => separator = Some(attr.value()), } } @@ -69,5 +77,7 @@ fn parse_attrs(attrs: &[Attr]) -> Result { prefix, description, rename: rename_rule, + parser, + separator, }) } diff --git a/src/enum_attributes.rs b/src/enum_attributes.rs index 728b80eb..92086683 100644 --- a/src/enum_attributes.rs +++ b/src/enum_attributes.rs @@ -1,9 +1,12 @@ -use crate::attr::{Attr, BotCommandAttribute}; +use crate::attr::Attr; +use crate::command::parse_attrs; +use crate::fields_parse::ParserType; pub struct CommandEnum { pub prefix: Option, pub description: Option, pub rename_rule: Option, + pub parser_type: ParserType, } impl CommandEnum { @@ -13,6 +16,12 @@ impl CommandEnum { let prefix = attrs.prefix; let description = attrs.description; let rename = attrs.rename; + let separator = attrs.separator; + let mut parser = attrs.parser.unwrap_or(ParserType::Default); + match (&mut parser, &separator) { + (ParserType::Split { separator }, Some(s)) => *separator = Some(s.clone()), + _ => {} + } if let Some(rename_rule) = &rename { match rename_rule.as_str() { "lowercase" => {} @@ -23,34 +32,7 @@ impl CommandEnum { prefix, description, rename_rule: rename, + parser_type: parser, }) } } - -struct CommandAttrs { - prefix: Option, - description: Option, - rename: Option, -} - -fn parse_attrs(attrs: &[Attr]) -> Result { - let mut prefix = None; - let mut description = None; - let mut rename_rule = None; - - for attr in attrs { - match attr.name() { - BotCommandAttribute::Prefix => prefix = Some(attr.value()), - BotCommandAttribute::Description => description = Some(attr.value()), - BotCommandAttribute::RenameRule => rename_rule = Some(attr.value()), - #[allow(unreachable_patterns)] - _ => return Err("unexpected attribute".to_owned()), - } - } - - Ok(CommandAttrs { - prefix, - description, - rename: rename_rule, - }) -} diff --git a/src/fields_parse.rs b/src/fields_parse.rs new file mode 100644 index 00000000..d641d657 --- /dev/null +++ b/src/fields_parse.rs @@ -0,0 +1,77 @@ +extern crate quote; + +use quote::__private::Span; +use quote::{quote, ToTokens}; +use syn::FieldsUnnamed; + +#[derive(Debug)] +pub enum ParserType { + Default, + Split { separator: Option }, + Custom(String), +} + +impl ParserType { + pub fn parse(data: &str) -> Self { + match data { + "default" => ParserType::Default, + "split" => ParserType::Split { separator: None }, + s => ParserType::Custom(s.to_owned()), + } + } +} + +pub fn impl_parse_args_unnamed( + data: &FieldsUnnamed, + variant: impl ToTokens, + parser_type: &ParserType, +) -> quote::__private::TokenStream { + let function_to_parse = match parser_type { + ParserType::Default => { + match data.unnamed.len() { + 1 => { + quote! { (|s: String| Ok((FromStr::from_str(&s).map_err(|_|ParseError::UncorrectFormat)?,)) ) } + } + _ => quote! { compile_error!("Expected 1 argument") }, + } + } + ParserType::Split { separator } => parser_with_separator( + &separator.clone().unwrap_or(" ".to_owned()), + data.unnamed.len(), + ), + ParserType::Custom(s) => { + let ident = syn::Ident::new(&s, Span::call_site()); + quote! { #ident } + } + }; + let get_arguments = quote! { let arguments = #function_to_parse(args)?; }; + let iter = 0..data.unnamed.len(); + let mut initialization = quote! {}; + for i in iter { + initialization.extend(quote! { arguments.#i, }) + } + let res = quote! { + { + #get_arguments + #variant(#initialization) + } + }; + res +} + +fn parser_with_separator(separator: &str, count_args: usize) -> quote::__private::TokenStream { + let inner = quote! { let splited = s.split(#separator).collect::>(); }; + let mut inner2 = quote! {}; + for i in 0..count_args { + inner2.extend( + quote! { FromStr::from_str(splited[#i]).map_err(|_|ParseError::UncorrectFormat)?, }, + ) + } + let res = quote! { + (|s: String| { + #inner + Ok((#inner2)) + }) + }; + res +} diff --git a/src/lib.rs b/src/lib.rs index 4e445232..dc6c1b69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,13 @@ mod attr; mod command; mod enum_attributes; +mod fields_parse; mod rename_rules; extern crate proc_macro; +extern crate quote; extern crate syn; +use crate::fields_parse::impl_parse_args_unnamed; use crate::{ attr::{Attr, VecAttrs}, command::Command, @@ -12,7 +15,7 @@ use crate::{ }; use proc_macro::TokenStream; use quote::{quote, ToTokens}; -use syn::{parse_macro_input, DeriveInput, Variant}; +use syn::{parse_macro_input, DeriveInput, Fields}; macro_rules! get_or_return { ($($some:tt)*) => { @@ -36,7 +39,7 @@ 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(|variant| variant).collect(); let mut variant_infos = vec![]; for variant in variants.iter() { @@ -57,15 +60,29 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { } } + let mut vec_impl_create = vec![]; + for (variant, info) in variants.iter().zip(variant_infos.iter()) { + let var = &variant.ident; + let variantt = quote! { Self::#var }; + match &variant.fields { + Fields::Unnamed(fields) => { + let parser = info.parser.as_ref().unwrap_or(&command_enum.parser_type); + vec_impl_create.push(impl_parse_args_unnamed(fields, variantt, parser)); + } + Fields::Unit => { + vec_impl_create.push(variantt); + } + _ => panic!("only unnamed fields"), // TODO: named fields + } + } + let ident = &input.ident; - let fn_try_from = impl_try_parse_command(&variants, &variant_infos, &command_enum); let fn_descriptions = impl_descriptions(&variant_infos, &command_enum); - let fn_parse = impl_parse(); + let fn_parse = impl_parse(&variant_infos, &command_enum, &vec_impl_create); let trait_impl = quote! { impl BotCommand for #ident { - #fn_try_from #fn_descriptions #fn_parse } @@ -74,23 +91,7 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { TokenStream::from(trait_impl) } -fn impl_try_parse_command(variants: &[&Variant], infos: &[Command], global: &CommandEnum) -> impl ToTokens { - let matching_values = infos.iter().map(|c| c.get_matched_value(global)); - let variant_ident = variants.iter().map(|variant| &variant.ident); - - quote! { - fn try_from(value: &str) -> Option { - match value { - #( - #matching_values => Some(Self::#variant_ident), - )* - _ => None - } - } - } -} - -fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> impl ToTokens { +fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> quote::__private::TokenStream { let global_description = if let Some(s) = &global.description { quote! { #s, "\n", } } else { @@ -105,31 +106,42 @@ fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> impl ToTokens { }); quote! { - fn descriptions() -> &'static str { - std::concat!(#global_description #(#command, #description, '\n'),*) + fn descriptions() -> String { + std::concat!(#global_description #(#command, #description, '\n'),*).to_string() } } } -fn impl_parse() -> impl ToTokens { +fn impl_parse( + infos: &[Command], + global: &CommandEnum, + variants_initialization: &[quote::__private::TokenStream], +) -> quote::__private::TokenStream { + let matching_values = infos.iter().map(|c| c.get_matched_value(global)); + quote! { - fn parse(s: &str, bot_name: N) -> Option<(Self, Vec<&str>)> + fn parse(s: &str, bot_name: N) -> Result where - N: Into + N: Into { - let mut words = s.split_whitespace(); - let mut splited = words.next()?.split('@'); - let command_raw = splited.next()?; - let bot = splited.next(); - let bot_name = bot_name.into(); - match bot { - Some(name) if name == bot_name => {} - None => {} - _ => return None, - } - let command = Self::try_from(command_raw)?; - Some((command, words.collect())) - } + let mut words = s.splitn(2, ' '); + let mut splited = words.next().ok_or(ParseError::UncorrectFormat)?.split('@'); + let command_raw = splited.next().ok_or(ParseError::UncorrectFormat)?; + let bot = splited.next(); + let bot_name = bot_name.into(); + match bot { + Some(name) if name == bot_name => {} + None => {} + Some(n) => return Err(ParseError::WrongBotName(n.to_string())), + } + let mut args = words.next().unwrap_or("").to_string(); + match command_raw { + #( + #matching_values => Ok(#variants_initialization), + )* + _ => Err(ParseError::UncorrectCommand(command_raw.to_string())), + } + } } } From 3f21329c3df74ca485afcc1357098aad1c4dd6f3 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Mon, 1 Jun 2020 21:13:30 +0300 Subject: [PATCH 024/755] added parsing of named fields --- src/command.rs | 7 +++-- src/fields_parse.rs | 62 ++++++++++++++++++++++++++++++--------------- src/lib.rs | 8 ++++-- 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/command.rs b/src/command.rs index bf99ddf3..fd201f4f 100644 --- a/src/command.rs +++ b/src/command.rs @@ -4,7 +4,6 @@ use crate::{ attr::{Attr, BotCommandAttribute}, rename_rules::rename_by_rule, }; -use crate::enum_attributes::CommandEnum; pub struct Command { pub prefix: Option, @@ -45,7 +44,11 @@ impl Command { } else { "/" }; - String::from(prefix) + &self.name + if let Some(rule) = &global_parameters.rename_rule { + String::from(prefix) + &rename_by_rule(&self.name, rule.as_str()) + } else { + String::from(prefix) + &self.name + } } } diff --git a/src/fields_parse.rs b/src/fields_parse.rs index d641d657..e06f421a 100644 --- a/src/fields_parse.rs +++ b/src/fields_parse.rs @@ -2,7 +2,7 @@ extern crate quote; use quote::__private::Span; use quote::{quote, ToTokens}; -use syn::FieldsUnnamed; +use syn::{FieldsUnnamed, FieldsNamed}; #[derive(Debug)] pub enum ParserType { @@ -26,25 +26,7 @@ pub fn impl_parse_args_unnamed( variant: impl ToTokens, parser_type: &ParserType, ) -> quote::__private::TokenStream { - let function_to_parse = match parser_type { - ParserType::Default => { - match data.unnamed.len() { - 1 => { - quote! { (|s: String| Ok((FromStr::from_str(&s).map_err(|_|ParseError::UncorrectFormat)?,)) ) } - } - _ => quote! { compile_error!("Expected 1 argument") }, - } - } - ParserType::Split { separator } => parser_with_separator( - &separator.clone().unwrap_or(" ".to_owned()), - data.unnamed.len(), - ), - ParserType::Custom(s) => { - let ident = syn::Ident::new(&s, Span::call_site()); - quote! { #ident } - } - }; - let get_arguments = quote! { let arguments = #function_to_parse(args)?; }; + let get_arguments = create_parser(parser_type, data.unnamed.len()); let iter = 0..data.unnamed.len(); let mut initialization = quote! {}; for i in iter { @@ -59,6 +41,46 @@ pub fn impl_parse_args_unnamed( res } +pub fn impl_parse_args_named( + data: &FieldsNamed, + variant: impl ToTokens, + parser_type: &ParserType, +) -> quote::__private::TokenStream { + let get_arguments = create_parser(parser_type, data.named.len()); + let i = 0..data.named.len(); + let name = data.named.iter().map(|f| f.ident.as_ref().unwrap()); + let res = quote! { + { + #get_arguments + #variant { #(#name: arguments.#i),* } + } + }; + res +} + + +fn create_parser(parser_type: &ParserType, count_args: usize) -> quote::__private::TokenStream { + let function_to_parse = match parser_type { + ParserType::Default => { + match count_args { + 1 => { + quote! { (|s: String| Ok((FromStr::from_str(&s).map_err(|_|ParseError::UncorrectFormat)?,)) ) } + } + _ => quote! { compile_error!("Expected 1 argument") }, + } + } + ParserType::Split { separator } => parser_with_separator( + &separator.clone().unwrap_or(" ".to_owned()), + count_args, + ), + ParserType::Custom(s) => { + let ident = syn::Ident::new(&s, Span::call_site()); + quote! { #ident } + } + }; + quote! { let arguments = #function_to_parse(args)?; } +} + fn parser_with_separator(separator: &str, count_args: usize) -> quote::__private::TokenStream { let inner = quote! { let splited = s.split(#separator).collect::>(); }; let mut inner2 = quote! {}; diff --git a/src/lib.rs b/src/lib.rs index dc6c1b69..aba3f73b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ mod rename_rules; extern crate proc_macro; extern crate quote; extern crate syn; -use crate::fields_parse::impl_parse_args_unnamed; +use crate::fields_parse::{impl_parse_args_unnamed, impl_parse_args_named}; use crate::{ attr::{Attr, VecAttrs}, command::Command, @@ -72,7 +72,10 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { Fields::Unit => { vec_impl_create.push(variantt); } - _ => panic!("only unnamed fields"), // TODO: named fields + Fields::Named(named) => { + let parser = info.parser.as_ref().unwrap_or(&command_enum.parser_type); + vec_impl_create.push(impl_parse_args_named(named, variantt, parser)); + } } } @@ -124,6 +127,7 @@ fn impl_parse( where N: Into { + use std::str::FromStr; let mut words = s.splitn(2, ' '); let mut splited = words.next().ok_or(ParseError::UncorrectFormat)?.split('@'); let command_raw = splited.next().ok_or(ParseError::UncorrectFormat)?; From a918eb4e507f8a4d83f6151a17db2c982339ab5b Mon Sep 17 00:00:00 2001 From: p0lunin Date: Mon, 1 Jun 2020 22:33:51 +0300 Subject: [PATCH 025/755] refactoring + fmt --- src/command.rs | 2 +- src/{enum_attributes.rs => command_enum.rs} | 0 src/fields_parse.rs | 42 +++++++++++++-------- src/lib.rs | 12 +++--- 4 files changed, 33 insertions(+), 23 deletions(-) rename src/{enum_attributes.rs => command_enum.rs} (100%) diff --git a/src/command.rs b/src/command.rs index fd201f4f..8a5f3a3a 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,4 +1,4 @@ -use crate::enum_attributes::CommandEnum; +use crate::command_enum::CommandEnum; use crate::fields_parse::ParserType; use crate::{ attr::{Attr, BotCommandAttribute}, diff --git a/src/enum_attributes.rs b/src/command_enum.rs similarity index 100% rename from src/enum_attributes.rs rename to src/command_enum.rs diff --git a/src/fields_parse.rs b/src/fields_parse.rs index e06f421a..3b38c38f 100644 --- a/src/fields_parse.rs +++ b/src/fields_parse.rs @@ -2,7 +2,7 @@ extern crate quote; use quote::__private::Span; use quote::{quote, ToTokens}; -use syn::{FieldsUnnamed, FieldsNamed}; +use syn::{FieldsNamed, FieldsUnnamed}; #[derive(Debug)] pub enum ParserType { @@ -58,41 +58,51 @@ pub fn impl_parse_args_named( res } - fn create_parser(parser_type: &ParserType, count_args: usize) -> quote::__private::TokenStream { let function_to_parse = match parser_type { - ParserType::Default => { - match count_args { - 1 => { - quote! { (|s: String| Ok((FromStr::from_str(&s).map_err(|_|ParseError::UncorrectFormat)?,)) ) } - } - _ => quote! { compile_error!("Expected 1 argument") }, + ParserType::Default => match count_args { + 1 => { + quote! { (|s: String| Ok((FromStr::from_str(&s).map_err(|_|ParseError::IncorrectFormat)?,)) ) } } + _ => quote! { compile_error!("Expected 1 argument") }, + }, + ParserType::Split { separator } => { + parser_with_separator(&separator.clone().unwrap_or(" ".to_owned()), count_args) } - ParserType::Split { separator } => parser_with_separator( - &separator.clone().unwrap_or(" ".to_owned()), - count_args, - ), ParserType::Custom(s) => { let ident = syn::Ident::new(&s, Span::call_site()); quote! { #ident } } }; - quote! { let arguments = #function_to_parse(args)?; } + quote! { + let arguments = #function_to_parse(args)?; + } } fn parser_with_separator(separator: &str, count_args: usize) -> quote::__private::TokenStream { - let inner = quote! { let splited = s.split(#separator).collect::>(); }; + let inner = quote! { let mut splited = s.split(#separator); }; let mut inner2 = quote! {}; for i in 0..count_args { inner2.extend( - quote! { FromStr::from_str(splited[#i]).map_err(|_|ParseError::UncorrectFormat)?, }, + quote! { FromStr::from_str(splited.next().ok_or(ParseError::TooFewArguments { + expected: #count_args, + found: #i, + message: format!("Expected but not found arg number {}", #i + 1), + })?).map_err(|_|ParseError::IncorrectFormat)?, }, ) } let res = quote! { (|s: String| { #inner - Ok((#inner2)) + let res = (#inner2); + match splited.next() { + Some(d) => Err(ParseError::TooManyArguments { + expected: #count_args, + found: #count_args + 1, + message: format!("Excess argument: {}", d), + }), + None => Ok(res) + } }) }; res diff --git a/src/lib.rs b/src/lib.rs index aba3f73b..ad083f9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,17 @@ mod attr; mod command; -mod enum_attributes; +mod command_enum; mod fields_parse; mod rename_rules; extern crate proc_macro; extern crate quote; extern crate syn; -use crate::fields_parse::{impl_parse_args_unnamed, impl_parse_args_named}; +use crate::fields_parse::{impl_parse_args_named, impl_parse_args_unnamed}; use crate::{ attr::{Attr, VecAttrs}, command::Command, - enum_attributes::CommandEnum, + command_enum::CommandEnum, }; use proc_macro::TokenStream; use quote::{quote, ToTokens}; @@ -129,8 +129,8 @@ fn impl_parse( { use std::str::FromStr; let mut words = s.splitn(2, ' '); - let mut splited = words.next().ok_or(ParseError::UncorrectFormat)?.split('@'); - let command_raw = splited.next().ok_or(ParseError::UncorrectFormat)?; + let mut splited = words.next().expect("First item will be always.").split('@'); + let command_raw = splited.next().expect("First item will be always."); let bot = splited.next(); let bot_name = bot_name.into(); match bot { @@ -143,7 +143,7 @@ fn impl_parse( #( #matching_values => Ok(#variants_initialization), )* - _ => Err(ParseError::UncorrectCommand(command_raw.to_string())), + _ => Err(ParseError::UnknownCommand(command_raw.to_string())), } } } From 4e6e6afc3edc419eab2f62dcc36f2708ccafc2c7 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Tue, 2 Jun 2020 21:11:23 +0300 Subject: [PATCH 026/755] import error into scope of parse function --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index ad083f9b..c789d14a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,11 +123,13 @@ fn impl_parse( let matching_values = infos.iter().map(|c| c.get_matched_value(global)); quote! { - fn parse(s: &str, bot_name: N) -> Result + fn parse(s: &str, bot_name: N) -> Result where N: Into { use std::str::FromStr; + use teloxide::utils::ParseError; + let mut words = s.splitn(2, ' '); let mut splited = words.next().expect("First item will be always.").split('@'); let command_raw = splited.next().expect("First item will be always."); From 87cbcf11407107734099b6d008fa7a659ce43df4 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 6 Jun 2020 20:12:18 +0300 Subject: [PATCH 027/755] fix --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c789d14a..49b1e1ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,12 +123,12 @@ fn impl_parse( let matching_values = infos.iter().map(|c| c.get_matched_value(global)); quote! { - fn parse(s: &str, bot_name: N) -> Result + fn parse(s: &str, bot_name: N) -> Result where N: Into { use std::str::FromStr; - use teloxide::utils::ParseError; + use teloxide::utils::command::ParseError; let mut words = s.splitn(2, ' '); let mut splited = words.next().expect("First item will be always.").split('@'); From 9fa60a75964dddc59ac36544db2788c8b983778f Mon Sep 17 00:00:00 2001 From: p0lunin Date: Wed, 24 Jun 2020 19:13:56 +0300 Subject: [PATCH 028/755] fixed rust could not infer types --- src/fields_parse.rs | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/fields_parse.rs b/src/fields_parse.rs index 3b38c38f..163b4f4e 100644 --- a/src/fields_parse.rs +++ b/src/fields_parse.rs @@ -2,7 +2,7 @@ extern crate quote; use quote::__private::Span; use quote::{quote, ToTokens}; -use syn::{FieldsNamed, FieldsUnnamed}; +use syn::{FieldsNamed, FieldsUnnamed, Type}; #[derive(Debug)] pub enum ParserType { @@ -26,7 +26,7 @@ pub fn impl_parse_args_unnamed( variant: impl ToTokens, parser_type: &ParserType, ) -> quote::__private::TokenStream { - let get_arguments = create_parser(parser_type, data.unnamed.len()); + let get_arguments = create_parser(parser_type, data.unnamed.iter().map(|f| &f.ty), data.unnamed.len()); let iter = 0..data.unnamed.len(); let mut initialization = quote! {}; for i in iter { @@ -46,7 +46,7 @@ pub fn impl_parse_args_named( variant: impl ToTokens, parser_type: &ParserType, ) -> quote::__private::TokenStream { - let get_arguments = create_parser(parser_type, data.named.len()); + let get_arguments = create_parser(parser_type, data.named.iter().map(|f| &f.ty), data.named.len()); let i = 0..data.named.len(); let name = data.named.iter().map(|f| f.ident.as_ref().unwrap()); let res = quote! { @@ -58,16 +58,16 @@ pub fn impl_parse_args_named( res } -fn create_parser(parser_type: &ParserType, count_args: usize) -> quote::__private::TokenStream { +fn create_parser<'a>(parser_type: &ParserType, types: impl Iterator, count_args: usize) -> quote::__private::TokenStream { let function_to_parse = match parser_type { ParserType::Default => match count_args { 1 => { - quote! { (|s: String| Ok((FromStr::from_str(&s).map_err(|_|ParseError::IncorrectFormat)?,)) ) } + quote! { (|s: String| Ok((s,))) } } _ => quote! { compile_error!("Expected 1 argument") }, }, ParserType::Split { separator } => { - parser_with_separator(&separator.clone().unwrap_or(" ".to_owned()), count_args) + parser_with_separator(&separator.clone().unwrap_or(" ".to_owned()), types, count_args) } ParserType::Custom(s) => { let ident = syn::Ident::new(&s, Span::call_site()); @@ -79,18 +79,17 @@ fn create_parser(parser_type: &ParserType, count_args: usize) -> quote::__privat } } -fn parser_with_separator(separator: &str, count_args: usize) -> quote::__private::TokenStream { +fn parser_with_separator<'a>(separator: &str, types: impl Iterator, count_args: usize) -> quote::__private::TokenStream { let inner = quote! { let mut splited = s.split(#separator); }; - let mut inner2 = quote! {}; - for i in 0..count_args { - inner2.extend( - quote! { FromStr::from_str(splited.next().ok_or(ParseError::TooFewArguments { - expected: #count_args, - found: #i, - message: format!("Expected but not found arg number {}", #i + 1), - })?).map_err(|_|ParseError::IncorrectFormat)?, }, - ) - } + let i = 0..count_args; + let inner2 = + quote! { + #(#types::from_str(splited.next().ok_or(ParseError::TooFewArguments { + expected: #count_args, + found: #i, + message: format!("Expected but not found arg number {}", #i + 1), + })?).map_err(|e|ParseError::IncorrectFormat({ let e: Box = e.into(); e }))?,)* + }; let res = quote! { (|s: String| { #inner From 4ce3d96e2032784244d8ce662759d4857bc5b0c1 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Wed, 24 Jun 2020 19:14:31 +0300 Subject: [PATCH 029/755] fmt --- src/fields_parse.rs | 47 ++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/fields_parse.rs b/src/fields_parse.rs index 163b4f4e..d6ca9c2d 100644 --- a/src/fields_parse.rs +++ b/src/fields_parse.rs @@ -26,7 +26,11 @@ pub fn impl_parse_args_unnamed( variant: impl ToTokens, parser_type: &ParserType, ) -> quote::__private::TokenStream { - let get_arguments = create_parser(parser_type, data.unnamed.iter().map(|f| &f.ty), data.unnamed.len()); + let get_arguments = create_parser( + parser_type, + data.unnamed.iter().map(|f| &f.ty), + data.unnamed.len(), + ); let iter = 0..data.unnamed.len(); let mut initialization = quote! {}; for i in iter { @@ -46,7 +50,11 @@ pub fn impl_parse_args_named( variant: impl ToTokens, parser_type: &ParserType, ) -> quote::__private::TokenStream { - let get_arguments = create_parser(parser_type, data.named.iter().map(|f| &f.ty), data.named.len()); + let get_arguments = create_parser( + parser_type, + data.named.iter().map(|f| &f.ty), + data.named.len(), + ); let i = 0..data.named.len(); let name = data.named.iter().map(|f| f.ident.as_ref().unwrap()); let res = quote! { @@ -58,7 +66,11 @@ pub fn impl_parse_args_named( res } -fn create_parser<'a>(parser_type: &ParserType, types: impl Iterator, count_args: usize) -> quote::__private::TokenStream { +fn create_parser<'a>( + parser_type: &ParserType, + types: impl Iterator, + count_args: usize, +) -> quote::__private::TokenStream { let function_to_parse = match parser_type { ParserType::Default => match count_args { 1 => { @@ -66,9 +78,11 @@ fn create_parser<'a>(parser_type: &ParserType, types: impl Iterator quote! { compile_error!("Expected 1 argument") }, }, - ParserType::Split { separator } => { - parser_with_separator(&separator.clone().unwrap_or(" ".to_owned()), types, count_args) - } + ParserType::Split { separator } => parser_with_separator( + &separator.clone().unwrap_or(" ".to_owned()), + types, + count_args, + ), ParserType::Custom(s) => { let ident = syn::Ident::new(&s, Span::call_site()); quote! { #ident } @@ -79,17 +93,20 @@ fn create_parser<'a>(parser_type: &ParserType, types: impl Iterator(separator: &str, types: impl Iterator, count_args: usize) -> quote::__private::TokenStream { +fn parser_with_separator<'a>( + separator: &str, + types: impl Iterator, + count_args: usize, +) -> quote::__private::TokenStream { let inner = quote! { let mut splited = s.split(#separator); }; let i = 0..count_args; - let inner2 = - quote! { - #(#types::from_str(splited.next().ok_or(ParseError::TooFewArguments { - expected: #count_args, - found: #i, - message: format!("Expected but not found arg number {}", #i + 1), - })?).map_err(|e|ParseError::IncorrectFormat({ let e: Box = e.into(); e }))?,)* - }; + let inner2 = quote! { + #(#types::from_str(splited.next().ok_or(ParseError::TooFewArguments { + expected: #count_args, + found: #i, + message: format!("Expected but not found arg number {}", #i + 1), + })?).map_err(|e|ParseError::IncorrectFormat({ let e: Box = e.into(); e }))?,)* + }; let res = quote! { (|s: String| { #inner From f47cd173be22ac9a07e96613bcc9cf5de8765a68 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Fri, 3 Jul 2020 20:42:58 +0300 Subject: [PATCH 030/755] prepare changelog to update to 0.3.0 --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f2f2f6a..85a7b6c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +Nothing + +## [0.3.0] - 2020-07-03 ### Changed - The description in `Cargo.toml` was changed to from "The teloxide's macros for internal usage" to "The teloxide's procedural macros". + - Now parsing of arguments happens using special function. There are 3 possible variants: + - Using `default` parser, which only put all text in one String field. + - Using `split` parser, which split all text by `separator` (by default is whitespace) and then use FromStr::from_str to construct value. + - Using custom separator. + - Now function `parse` return Result instead of Option. ### Added - This `CHANGELOG.md`. - `.gitignore`. - + - `#[parse_with]` attribute. + - `#[separator='%sep%']` attribute. ## [0.1.2] - 2020-02-24 ### Changed From 3357bd5947dd97e3e0a09e15b89a693d1b27d473 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Fri, 3 Jul 2020 20:47:03 +0300 Subject: [PATCH 031/755] fixed clippy errors --- src/command_enum.rs | 5 ++--- src/fields_parse.rs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/command_enum.rs b/src/command_enum.rs index f09721cb..0bfc1cdc 100644 --- a/src/command_enum.rs +++ b/src/command_enum.rs @@ -19,9 +19,8 @@ impl CommandEnum { let rename = attrs.rename; let separator = attrs.separator; let mut parser = attrs.parser.unwrap_or(ParserType::Default); - match (&mut parser, &separator) { - (ParserType::Split { separator }, Some(s)) => *separator = Some(s.clone()), - _ => {} + if let (ParserType::Split { separator }, Some(s)) = (&mut parser, &separator) { + *separator = Some(s.clone()) } if let Some(rename_rule) = &rename { match rename_rule.as_str() { diff --git a/src/fields_parse.rs b/src/fields_parse.rs index d6ca9c2d..d6410353 100644 --- a/src/fields_parse.rs +++ b/src/fields_parse.rs @@ -79,7 +79,7 @@ fn create_parser<'a>( _ => quote! { compile_error!("Expected 1 argument") }, }, ParserType::Split { separator } => parser_with_separator( - &separator.clone().unwrap_or(" ".to_owned()), + &separator.clone().unwrap_or_else(|| " ".to_owned()), types, count_args, ), From cf2347214a40f1c70e85fb0ca697dcf6f6d4ac39 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Fri, 3 Jul 2020 20:50:26 +0300 Subject: [PATCH 032/755] changed readme --- Readme.md | 128 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 114 insertions(+), 14 deletions(-) diff --git a/Readme.md b/Readme.md index 04e9e074..72737dc5 100644 --- a/Readme.md +++ b/Readme.md @@ -1,23 +1,27 @@ # teloxide-macros The teloxide's procedural macros. -## Example +# Example ```rust use teloxide::utils::command::BotCommand; + +type UnitOfTime = u8; + #[derive(BotCommand, PartialEq, Debug)] -#[command(rename = "lowercase")] +#[command(rename = "lowercase", parse_with = "split")] enum AdminCommand { - Mute, - Ban, + Mute(UnitOfTime, char), + Ban(UnitOfTime, char), } -let (command, args) = AdminCommand::parse("/ban 5 h", "bot_name").unwrap(); -assert_eq!(command, AdminCommand::Ban); -assert_eq!(args, vec!["5", "h"]); + +let command = AdminCommand::parse("/ban 5 h", "bot_name").unwrap(); +assert_eq!(command, AdminCommand::Ban(5, 'h')); ``` + ## Enum attributes 1. `#[command(rename = "rule")]` -Rename all commands by rule. Allowed rules are `lowercase`. If you will not -use this attribute, commands will be parsed by their original names. +Rename all commands by `rule`. Allowed rules are `lowercase`. If you will +not use this attribute, commands will be parsed by their original names. 2. `#[command(prefix = "prefix")]` Change a prefix for all commands (the default is `/`). @@ -25,15 +29,111 @@ Change a prefix for all commands (the default is `/`). 3. `#[command(description = "description")]` Add a sumary description of commands before all commands. + 4. `#[command(parse_with = "parser")]` +Change the parser of arguments. Possible values: + - `default` - the same as the unspecified parser. It only puts all text + after the first space into the first argument, which must implement + [`FromStr`]. + +### Example +```rust +use teloxide::utils::command::BotCommand; + +#[derive(BotCommand, PartialEq, Debug)] +#[command(rename = "lowercase")] +enum Command { + Text(String), +} + +let command = Command::parse("/text hello my dear friend!", "").unwrap(); +assert_eq!(command, Command::Text("hello my dear friend!".to_string())); +``` + + - `split` - separates a messsage by a given separator (the default is the + space character) and parses each part into the corresponding arguments, + which must implement [`FromStr`]. + +### Example +```rust +use teloxide::utils::command::BotCommand; + +#[derive(BotCommand, PartialEq, Debug)] +#[command(rename = "lowercase", parse_with = "split")] +enum Command { + Nums(u8, u16, i32), +} + +let command = Command::parse("/nums 1 32 -5", "").unwrap(); +assert_eq!(command, Command::Nums(1, 32, -5)); +``` + +5. `#[command(separator = "sep")]` +Specify separator used by the `split` parser. It will be ignored when +accompanied by another type of parsers. + +### Example +```rust +use teloxide::utils::command::BotCommand; + +#[derive(BotCommand, PartialEq, Debug)] +#[command(rename = "lowercase", parse_with = "split", separator = "|")] +enum Command { + Nums(u8, u16, i32), +} + +let command = Command::parse("/nums 1|32|5", "").unwrap(); +assert_eq!(command, Command::Nums(1, 32, 5)); +``` + ## Variant attributes +All variant attributes override the corresponding `enum` attributes. + 1. `#[command(rename = "rule")]` Rename one command by a rule. Allowed rules are `lowercase`, `%some_name%`, where `%some_name%` is any string, a new name. - 2. `#[command(prefix = "prefix")]` -Change a prefix for one command (the default is `/`). + 2. `#[command(parse_with = "parser")]` +One more option is available for variants. + - `custom_parser` - your own parser of the signature `fn(String) -> + Result`, where `Tuple` corresponds to the variant's +arguments. - 3. `#[command(description = "description")]` -Add a description of one command. +### Example +```rust +use teloxide::utils::command::{BotCommand, ParseError}; -All variant attributes overlap the `enum` attributes. \ No newline at end of file +fn accept_two_digits(input: String) -> Result<(u8,), ParseError> { + match input.len() { + 2 => { + let num = input + .parse::() + .map_err(|e| ParseError::IncorrectFormat(e.into()))?; + Ok((num,)) + } + len => Err(ParseError::Custom( + format!("Only 2 digits allowed, not {}", len).into(), + )), + } +} + +#[derive(BotCommand, PartialEq, Debug)] +#[command(rename = "lowercase")] +enum Command { + #[command(parse_with = "accept_two_digits")] + Num(u8), +} + +let command = Command::parse("/num 12", "").unwrap(); +assert_eq!(command, Command::Num(12)); +let command = Command::parse("/num 333", ""); +assert!(command.is_err()); +``` + + 3. `#[command(prefix = "prefix")]` + 4. `#[command(description = "description")]` + 5. `#[command(separator = "sep")]` + +Analogous to the descriptions above. + +[`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html +[`BotCommand`]: crate::utils::command::BotCommand From 7a05f021bd4e8707f3f59318d1969b9ba4a36101 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Fri, 3 Jul 2020 20:55:25 +0300 Subject: [PATCH 033/755] delete readme --- Readme.md | 139 ------------------------------------------------------ 1 file changed, 139 deletions(-) delete mode 100644 Readme.md diff --git a/Readme.md b/Readme.md deleted file mode 100644 index 72737dc5..00000000 --- a/Readme.md +++ /dev/null @@ -1,139 +0,0 @@ -# teloxide-macros -The teloxide's procedural macros. - -# Example -```rust -use teloxide::utils::command::BotCommand; - -type UnitOfTime = u8; - -#[derive(BotCommand, PartialEq, Debug)] -#[command(rename = "lowercase", parse_with = "split")] -enum AdminCommand { - Mute(UnitOfTime, char), - Ban(UnitOfTime, char), -} - -let command = AdminCommand::parse("/ban 5 h", "bot_name").unwrap(); -assert_eq!(command, AdminCommand::Ban(5, 'h')); -``` - -## Enum attributes - 1. `#[command(rename = "rule")]` -Rename all commands by `rule`. Allowed rules are `lowercase`. If you will -not use this attribute, commands will be parsed by their original names. - - 2. `#[command(prefix = "prefix")]` -Change a prefix for all commands (the default is `/`). - - 3. `#[command(description = "description")]` -Add a sumary description of commands before all commands. - - 4. `#[command(parse_with = "parser")]` -Change the parser of arguments. Possible values: - - `default` - the same as the unspecified parser. It only puts all text - after the first space into the first argument, which must implement - [`FromStr`]. - -### Example -```rust -use teloxide::utils::command::BotCommand; - -#[derive(BotCommand, PartialEq, Debug)] -#[command(rename = "lowercase")] -enum Command { - Text(String), -} - -let command = Command::parse("/text hello my dear friend!", "").unwrap(); -assert_eq!(command, Command::Text("hello my dear friend!".to_string())); -``` - - - `split` - separates a messsage by a given separator (the default is the - space character) and parses each part into the corresponding arguments, - which must implement [`FromStr`]. - -### Example -```rust -use teloxide::utils::command::BotCommand; - -#[derive(BotCommand, PartialEq, Debug)] -#[command(rename = "lowercase", parse_with = "split")] -enum Command { - Nums(u8, u16, i32), -} - -let command = Command::parse("/nums 1 32 -5", "").unwrap(); -assert_eq!(command, Command::Nums(1, 32, -5)); -``` - -5. `#[command(separator = "sep")]` -Specify separator used by the `split` parser. It will be ignored when -accompanied by another type of parsers. - -### Example -```rust -use teloxide::utils::command::BotCommand; - -#[derive(BotCommand, PartialEq, Debug)] -#[command(rename = "lowercase", parse_with = "split", separator = "|")] -enum Command { - Nums(u8, u16, i32), -} - -let command = Command::parse("/nums 1|32|5", "").unwrap(); -assert_eq!(command, Command::Nums(1, 32, 5)); -``` - -## Variant attributes -All variant attributes override the corresponding `enum` attributes. - - 1. `#[command(rename = "rule")]` -Rename one command by a rule. Allowed rules are `lowercase`, `%some_name%`, -where `%some_name%` is any string, a new name. - - 2. `#[command(parse_with = "parser")]` -One more option is available for variants. - - `custom_parser` - your own parser of the signature `fn(String) -> - Result`, where `Tuple` corresponds to the variant's -arguments. - -### Example -```rust -use teloxide::utils::command::{BotCommand, ParseError}; - -fn accept_two_digits(input: String) -> Result<(u8,), ParseError> { - match input.len() { - 2 => { - let num = input - .parse::() - .map_err(|e| ParseError::IncorrectFormat(e.into()))?; - Ok((num,)) - } - len => Err(ParseError::Custom( - format!("Only 2 digits allowed, not {}", len).into(), - )), - } -} - -#[derive(BotCommand, PartialEq, Debug)] -#[command(rename = "lowercase")] -enum Command { - #[command(parse_with = "accept_two_digits")] - Num(u8), -} - -let command = Command::parse("/num 12", "").unwrap(); -assert_eq!(command, Command::Num(12)); -let command = Command::parse("/num 333", ""); -assert!(command.is_err()); -``` - - 3. `#[command(prefix = "prefix")]` - 4. `#[command(description = "description")]` - 5. `#[command(separator = "sep")]` - -Analogous to the descriptions above. - -[`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html -[`BotCommand`]: crate::utils::command::BotCommand From d21dcd678cb4e76e48f38e02670c978654df333e Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 3 Jul 2020 23:59:10 +0600 Subject: [PATCH 034/755] Update Cargo.toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6342ee71..1d497962 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teloxide-macros" -version = "0.2.1" +version = "0.3.0" description = "The teloxide's procedural macros" authors = ["p0lunin "] license = "MIT" From 8be96a34889ce5866986675b135562a4c4044c61 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 4 Jul 2020 00:05:40 +0600 Subject: [PATCH 035/755] Update CHANGELOG.md --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85a7b6c1..f581075d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,15 @@ Nothing - Using custom separator. - Now function `parse` return Result instead of Option. +## [0.2.1] - 2020-02-25 +### Changed + - The description in `Cargo.toml` was changed to from "The teloxide's macros for internal usage" to "The teloxide's procedural macros". + +### Added + - This `CHANGELOG.md`. + - `.gitignore`. + - The functionality to parse commands only with a correct bot's name (breaks backwards compatibility). + ### Added - This `CHANGELOG.md`. - `.gitignore`. From 449a82d8394d26e8c7973e5d96c38d5b47037eb2 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 4 Jul 2020 00:07:56 +0600 Subject: [PATCH 036/755] Update CHANGELOG.md --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f581075d..bac4a87a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,12 @@ Nothing - Using custom separator. - Now function `parse` return Result instead of Option. +### Added + - This `CHANGELOG.md`. + - `.gitignore`. + - `#[parse_with]` attribute. + - `#[separator='%sep%']` attribute. + ## [0.2.1] - 2020-02-25 ### Changed - The description in `Cargo.toml` was changed to from "The teloxide's macros for internal usage" to "The teloxide's procedural macros". @@ -25,12 +31,6 @@ Nothing - `.gitignore`. - The functionality to parse commands only with a correct bot's name (breaks backwards compatibility). -### Added - - This `CHANGELOG.md`. - - `.gitignore`. - - `#[parse_with]` attribute. - - `#[separator='%sep%']` attribute. - ## [0.1.2] - 2020-02-24 ### Changed - The same as v0.1.1, but fixes [the issue](https://github.com/teloxide/teloxide/issues/176) about backwards compatibility. From 7d5d070bd795d0ff35aa5a2211104b91d7a680d7 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 4 Jul 2020 10:37:13 +0300 Subject: [PATCH 037/755] added option to remove command from description --- src/lib.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 49b1e1ae..1a16ccd1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,13 +104,19 @@ fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> quote::__privat let description = infos.iter().map(|info| { info.description .as_deref() - .map(|e| format!(" - {}", e)) + .map(|e| if e != "off" { format!(" - {}", e) } else { e.to_string() }) .unwrap_or_default() }); + let result_iter = command.zip(description).map(|(c, d)| { + match &d == "off" { + true => quote! {}, + false => quote! { #c, #d, '\n', } + } + }); quote! { fn descriptions() -> String { - std::concat!(#global_description #(#command, #description, '\n'),*).to_string() + std::concat!(#global_description #(#result_iter)*).to_string() } } } From 00513c74609cba999d4c7692a6bbf8fb86ab7fec Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 4 Jul 2020 10:39:28 +0300 Subject: [PATCH 038/755] changed changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bac4a87a..23a69c31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] -Nothing +## [0.3.1] +### Added + - Now you can remove command from showing in descriptions by defining `description` attribute as `"off"`. ## [0.3.0] - 2020-07-03 ### Changed From 8eda469fc9ed0ff8eea30648ec23ae929730c11b Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 4 Jul 2020 10:39:41 +0300 Subject: [PATCH 039/755] up version to 0.3.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1d497962..557f7802 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teloxide-macros" -version = "0.3.0" +version = "0.3.1" description = "The teloxide's procedural macros" authors = ["p0lunin "] license = "MIT" From bc388ca06ec584cfee10178846f2dd078f09182d Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 4 Jul 2020 10:42:19 +0300 Subject: [PATCH 040/755] fix clippy errors --- src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1a16ccd1..50bb9b02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,9 +108,10 @@ fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> quote::__privat .unwrap_or_default() }); let result_iter = command.zip(description).map(|(c, d)| { - match &d == "off" { - true => quote! {}, - false => quote! { #c, #d, '\n', } + if &d == "off" { + quote! {} + } else { + quote! { #c, #d, '\n', } } }); From 5f3436204415c64ac3c2963a216a4adc04d112db Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 4 Jul 2020 10:43:56 +0300 Subject: [PATCH 041/755] fmt --- src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 50bb9b02..1e9c9d51 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,7 +104,13 @@ fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> quote::__privat let description = infos.iter().map(|info| { info.description .as_deref() - .map(|e| if e != "off" { format!(" - {}", e) } else { e.to_string() }) + .map(|e| { + if e != "off" { + format!(" - {}", e) + } else { + e.to_string() + } + }) .unwrap_or_default() }); let result_iter = command.zip(description).map(|(c, d)| { From c5fcfd21fdc09b5e316aed6509f33a8fbd0ac60f Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 4 Jul 2020 13:56:27 +0600 Subject: [PATCH 042/755] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23a69c31..d0a00d67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.3.1] +## [0.3.1] - 2020-07-04 ### Added - Now you can remove command from showing in descriptions by defining `description` attribute as `"off"`. From 4b91245eae9d384a8d656f965a449f2ec15e09e3 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Fri, 10 Jul 2020 13:30:48 +0300 Subject: [PATCH 043/755] added bounds for Error --- src/fields_parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fields_parse.rs b/src/fields_parse.rs index d6410353..dffaf9f9 100644 --- a/src/fields_parse.rs +++ b/src/fields_parse.rs @@ -105,7 +105,7 @@ fn parser_with_separator<'a>( expected: #count_args, found: #i, message: format!("Expected but not found arg number {}", #i + 1), - })?).map_err(|e|ParseError::IncorrectFormat({ let e: Box = e.into(); e }))?,)* + })?).map_err(|e|ParseError::IncorrectFormat({ let e: Box = e.into(); e }))?,)* }; let res = quote! { (|s: String| { From ec9c540839730be08dbb150b1ef49973e071b19c Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Thu, 23 Jul 2020 23:56:25 +0600 Subject: [PATCH 044/755] Add #[handler], #[bot_dialogue] --- Cargo.toml | 2 +- src/lib.rs | 43 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 557f7802..b9af08b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ edition = "2018" [dependencies] quote = "1.0.3" -syn = "1.0.13" +syn = { version = "1.0.13", features = ["full"] } [lib] proc-macro = true diff --git a/src/lib.rs b/src/lib.rs index 1e9c9d51..9c26f972 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +// TODO: refactor this shit. + mod attr; mod command; mod command_enum; @@ -15,7 +17,46 @@ use crate::{ }; use proc_macro::TokenStream; use quote::{quote, ToTokens}; -use syn::{parse_macro_input, DeriveInput, Fields}; +use syn::{parse_macro_input, DeriveInput, Fields, ItemEnum}; + +use std::fmt::Write; + +#[proc_macro_attribute] +pub fn handler(_attr: TokenStream, item: TokenStream) -> TokenStream { + item +} + +#[proc_macro_attribute] +pub fn bot_dialogue(_attr: TokenStream, item: TokenStream) -> TokenStream { + let input = parse_macro_input!(item as ItemEnum); + let mut dispatch_fn = "".to_owned(); + + write!(dispatch_fn, "impl {0} {{ pub fn dispatch(self, cx: teloxide::dispatching::UpdateWithCx) -> TransitionOut<{0}> {{ match self {{", input.ident).unwrap(); + + for variant in input.variants.iter() { + if let Some(handler) = variant + .attrs + .iter() + .find(|attr| match attr.path.get_ident() { + Some(ident) => ident == "handler", + None => false, + }) + { + let mut handler_fn = handler.tokens.to_string()[1..].to_owned(); + handler_fn.pop(); + + write!( + dispatch_fn, + "{}::{}(state) => {}(cx, state).await,", + input.ident, variant.ident, handler_fn + ) + .unwrap(); + } + } + + write!(dispatch_fn, "}} }} }}").unwrap(); + dispatch_fn.parse().unwrap() +} macro_rules! get_or_return { ($($some:tt)*) => { From 17a9b4d3dffa5f87a53e1649becabaf7553fe653 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 24 Jul 2020 00:22:52 +0600 Subject: [PATCH 045/755] Fix errors --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 9c26f972..19aa1462 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ pub fn bot_dialogue(_attr: TokenStream, item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemEnum); let mut dispatch_fn = "".to_owned(); - write!(dispatch_fn, "impl {0} {{ pub fn dispatch(self, cx: teloxide::dispatching::UpdateWithCx) -> TransitionOut<{0}> {{ match self {{", input.ident).unwrap(); + write!(dispatch_fn, "impl {} {{ pub async fn dispatch(self, cx: teloxide::dispatching::UpdateWithCx) -> TransitionOut {{ match self {{", input.ident).unwrap(); for variant in input.variants.iter() { if let Some(handler) = variant From a18ef6fc9431ddcb44de8c8219242fd6cad4cf2b Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 24 Jul 2020 00:32:30 +0600 Subject: [PATCH 046/755] Describe a full path to TransitionOut --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 19aa1462..a0634fb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ pub fn bot_dialogue(_attr: TokenStream, item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemEnum); let mut dispatch_fn = "".to_owned(); - write!(dispatch_fn, "impl {} {{ pub async fn dispatch(self, cx: teloxide::dispatching::UpdateWithCx) -> TransitionOut {{ match self {{", input.ident).unwrap(); + write!(dispatch_fn, "impl {} {{ pub async fn dispatch(self, cx: teloxide::dispatching::UpdateWithCx) -> teloxide::dispatching::dialogue::TransitionOut {{ match self {{", input.ident).unwrap(); for variant in input.variants.iter() { if let Some(handler) = variant From 15e8e95952b3a72a7f9aefcdaa6614292657ab69 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 24 Jul 2020 00:49:34 +0600 Subject: [PATCH 047/755] Generate an enumeration itself too --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index a0634fb6..066b53eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,9 +28,11 @@ pub fn handler(_attr: TokenStream, item: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn bot_dialogue(_attr: TokenStream, item: TokenStream) -> TokenStream { + let item_cloned = item.clone(); let input = parse_macro_input!(item as ItemEnum); let mut dispatch_fn = "".to_owned(); + write!(dispatch_fn, "{}", item_cloned).unwrap(); write!(dispatch_fn, "impl {} {{ pub async fn dispatch(self, cx: teloxide::dispatching::UpdateWithCx) -> teloxide::dispatching::dialogue::TransitionOut {{ match self {{", input.ident).unwrap(); for variant in input.variants.iter() { From 40ab1b19c25cd6d860cf4d2eaa937260a49ef6c6 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 24 Jul 2020 18:04:47 +0600 Subject: [PATCH 048/755] Derive BotDialogue --- src/lib.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 066b53eb..abab604d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,19 +21,12 @@ use syn::{parse_macro_input, DeriveInput, Fields, ItemEnum}; use std::fmt::Write; -#[proc_macro_attribute] -pub fn handler(_attr: TokenStream, item: TokenStream) -> TokenStream { - item -} - -#[proc_macro_attribute] -pub fn bot_dialogue(_attr: TokenStream, item: TokenStream) -> TokenStream { - let item_cloned = item.clone(); +#[proc_macro_derive(BotDialogue, attributes(handler))] +pub fn derive_bot_dialogue(item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemEnum); let mut dispatch_fn = "".to_owned(); - write!(dispatch_fn, "{}", item_cloned).unwrap(); - write!(dispatch_fn, "impl {} {{ pub async fn dispatch(self, cx: teloxide::dispatching::UpdateWithCx) -> teloxide::dispatching::dialogue::TransitionOut {{ match self {{", input.ident).unwrap(); + write!(dispatch_fn, "impl teloxide::dispatching::dialogue::BotDialogue for {} {{ fn dispatch(self, cx: teloxide::dispatching::UpdateWithCx) -> futures::future::BoxFuture<'static, teloxide::dispatching::dialogue::TransitionOut> {{ futures::future::FutureExt::boxed(async {{ match self {{", input.ident).unwrap(); for variant in input.variants.iter() { if let Some(handler) = variant @@ -56,7 +49,7 @@ pub fn bot_dialogue(_attr: TokenStream, item: TokenStream) -> TokenStream { } } - write!(dispatch_fn, "}} }} }}").unwrap(); + write!(dispatch_fn, "}} }}) }} }}").unwrap(); dispatch_fn.parse().unwrap() } From f0ab748cce9a9e918a73722902b4c4c3c5e00e12 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 24 Jul 2020 18:29:30 +0600 Subject: [PATCH 049/755] Use #[transition] instead of #[handler] --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index abab604d..ba772054 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ use syn::{parse_macro_input, DeriveInput, Fields, ItemEnum}; use std::fmt::Write; -#[proc_macro_derive(BotDialogue, attributes(handler))] +#[proc_macro_derive(BotDialogue, attributes(transition))] pub fn derive_bot_dialogue(item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemEnum); let mut dispatch_fn = "".to_owned(); From 878e4370af5bcf7ae71fa250b7bdbb55c81501bf Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 24 Jul 2020 19:28:02 +0600 Subject: [PATCH 050/755] Use TransitionIn --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index ba772054..cedccd6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ pub fn derive_bot_dialogue(item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemEnum); let mut dispatch_fn = "".to_owned(); - write!(dispatch_fn, "impl teloxide::dispatching::dialogue::BotDialogue for {} {{ fn dispatch(self, cx: teloxide::dispatching::UpdateWithCx) -> futures::future::BoxFuture<'static, teloxide::dispatching::dialogue::TransitionOut> {{ futures::future::FutureExt::boxed(async {{ match self {{", input.ident).unwrap(); + write!(dispatch_fn, "impl teloxide::dispatching::dialogue::BotDialogue for {} {{ fn dispatch(self, cx: teloxide::dispatching::dialogue::TransitionIn) -> futures::future::BoxFuture<'static, teloxide::dispatching::dialogue::TransitionOut> {{ futures::future::FutureExt::boxed(async {{ match self {{", input.ident).unwrap(); for variant in input.variants.iter() { if let Some(handler) = variant From 22f8d22a54eca0e2eb53e21bf2a32f0caa095fdd Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 24 Jul 2020 19:30:40 +0600 Subject: [PATCH 051/755] Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0a00d67..d61f6809 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [unreleased] + +### Added + - `#[derive(BotDialogue)]` with `#[transition( Date: Fri, 24 Jul 2020 19:31:48 +0600 Subject: [PATCH 052/755] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d61f6809..49359c8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `#[derive(BotDialogue)]` with `#[transition( Date: Fri, 24 Jul 2020 19:35:08 +0600 Subject: [PATCH 053/755] Check for 'transition', not 'handler' --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index cedccd6d..bf2e7712 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,7 @@ pub fn derive_bot_dialogue(item: TokenStream) -> TokenStream { .attrs .iter() .find(|attr| match attr.path.get_ident() { - Some(ident) => ident == "handler", + Some(ident) => ident == "transition", None => false, }) { From bf173dec8ac1d9866058c8c2c91cc6719e89cce9 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 24 Jul 2020 19:36:22 +0600 Subject: [PATCH 054/755] Add rustfmt.toml --- rustfmt.toml | 7 +++++ src/command.rs | 20 ++++++------- src/command_enum.rs | 8 +++--- src/fields_parse.rs | 3 +- src/lib.rs | 68 ++++++++++++++++++++++++++++----------------- 5 files changed, 64 insertions(+), 42 deletions(-) create mode 100644 rustfmt.toml diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..1907f64f --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,7 @@ +format_code_in_doc_comments = true +wrap_comments = true +format_strings = true +max_width = 80 +merge_imports = true +use_small_heuristics = "Max" +use_field_init_shorthand = true diff --git a/src/command.rs b/src/command.rs index 8a5f3a3a..31aa092e 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,7 +1,7 @@ -use crate::command_enum::CommandEnum; -use crate::fields_parse::ParserType; use crate::{ attr::{Attr, BotCommandAttribute}, + command_enum::CommandEnum, + fields_parse::ParserType, rename_rules::rename_by_rule, }; @@ -27,13 +27,7 @@ impl Command { new_name = rename_by_rule(name, &rename_rule); renamed = true; } - Ok(Self { - prefix, - description, - parser, - name: new_name, - renamed, - }) + Ok(Self { prefix, description, parser, name: new_name, renamed }) } pub fn get_matched_value(&self, global_parameters: &CommandEnum) -> String { @@ -70,9 +64,13 @@ pub 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()), - BotCommandAttribute::CustomParser => parser = Some(ParserType::parse(&attr.value())), + BotCommandAttribute::CustomParser => { + parser = Some(ParserType::parse(&attr.value())) + } BotCommandAttribute::Separator => separator = Some(attr.value()), } } diff --git a/src/command_enum.rs b/src/command_enum.rs index 0bfc1cdc..e3c7e531 100644 --- a/src/command_enum.rs +++ b/src/command_enum.rs @@ -1,6 +1,4 @@ -use crate::attr::Attr; -use crate::command::parse_attrs; -use crate::fields_parse::ParserType; +use crate::{attr::Attr, command::parse_attrs, fields_parse::ParserType}; #[derive(Debug)] pub struct CommandEnum { @@ -19,7 +17,9 @@ impl CommandEnum { let rename = attrs.rename; let separator = attrs.separator; let mut parser = attrs.parser.unwrap_or(ParserType::Default); - if let (ParserType::Split { separator }, Some(s)) = (&mut parser, &separator) { + if let (ParserType::Split { separator }, Some(s)) = + (&mut parser, &separator) + { *separator = Some(s.clone()) } if let Some(rename_rule) = &rename { diff --git a/src/fields_parse.rs b/src/fields_parse.rs index dffaf9f9..56e2a80d 100644 --- a/src/fields_parse.rs +++ b/src/fields_parse.rs @@ -1,7 +1,6 @@ extern crate quote; -use quote::__private::Span; -use quote::{quote, ToTokens}; +use quote::{__private::Span, quote, ToTokens}; use syn::{FieldsNamed, FieldsUnnamed, Type}; #[derive(Debug)] diff --git a/src/lib.rs b/src/lib.rs index bf2e7712..b961d30c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,11 +9,11 @@ mod rename_rules; extern crate proc_macro; extern crate quote; extern crate syn; -use crate::fields_parse::{impl_parse_args_named, impl_parse_args_unnamed}; use crate::{ attr::{Attr, VecAttrs}, command::Command, command_enum::CommandEnum, + fields_parse::{impl_parse_args_named, impl_parse_args_unnamed}, }; use proc_macro::TokenStream; use quote::{quote, ToTokens}; @@ -26,13 +26,20 @@ pub fn derive_bot_dialogue(item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemEnum); let mut dispatch_fn = "".to_owned(); - write!(dispatch_fn, "impl teloxide::dispatching::dialogue::BotDialogue for {} {{ fn dispatch(self, cx: teloxide::dispatching::dialogue::TransitionIn) -> futures::future::BoxFuture<'static, teloxide::dispatching::dialogue::TransitionOut> {{ futures::future::FutureExt::boxed(async {{ match self {{", input.ident).unwrap(); + write!( + dispatch_fn, + "impl teloxide::dispatching::dialogue::BotDialogue for {} {{ fn \ + dispatch(self, cx: teloxide::dispatching::dialogue::TransitionIn) -> \ + futures::future::BoxFuture<'static, \ + teloxide::dispatching::dialogue::TransitionOut> {{ \ + futures::future::FutureExt::boxed(async {{ match self {{", + input.ident + ) + .unwrap(); for variant in input.variants.iter() { - if let Some(handler) = variant - .attrs - .iter() - .find(|attr| match attr.path.get_ident() { + if let Some(handler) = + variant.attrs.iter().find(|attr| match attr.path.get_ident() { Some(ident) => ident == "transition", None => false, }) @@ -75,7 +82,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(|variant| variant).collect(); + let variants: Vec<&syn::Variant> = + data_enum.variants.iter().map(|variant| variant).collect(); let mut variant_infos = vec![]; for variant in variants.iter() { @@ -102,15 +110,19 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { let variantt = quote! { Self::#var }; match &variant.fields { Fields::Unnamed(fields) => { - let parser = info.parser.as_ref().unwrap_or(&command_enum.parser_type); - vec_impl_create.push(impl_parse_args_unnamed(fields, variantt, parser)); + let parser = + info.parser.as_ref().unwrap_or(&command_enum.parser_type); + vec_impl_create + .push(impl_parse_args_unnamed(fields, variantt, parser)); } Fields::Unit => { vec_impl_create.push(variantt); } Fields::Named(named) => { - let parser = info.parser.as_ref().unwrap_or(&command_enum.parser_type); - vec_impl_create.push(impl_parse_args_named(named, variantt, parser)); + let parser = + info.parser.as_ref().unwrap_or(&command_enum.parser_type); + vec_impl_create + .push(impl_parse_args_named(named, variantt, parser)); } } } @@ -130,25 +142,29 @@ pub fn derive_telegram_command_enum(tokens: TokenStream) -> TokenStream { TokenStream::from(trait_impl) } -fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> quote::__private::TokenStream { +fn impl_descriptions( + infos: &[Command], + global: &CommandEnum, +) -> quote::__private::TokenStream { let global_description = if let Some(s) = &global.description { quote! { #s, "\n", } } else { quote! {} }; let command = infos.iter().map(|c| c.get_matched_value(global)); - let description = infos.iter().map(|info| { - info.description - .as_deref() - .map(|e| { - if e != "off" { - format!(" - {}", e) - } else { - e.to_string() - } - }) - .unwrap_or_default() - }); + let description = + infos.iter().map(|info| { + info.description + .as_deref() + .map(|e| { + if e != "off" { + format!(" - {}", e) + } else { + e.to_string() + } + }) + .unwrap_or_default() + }); let result_iter = command.zip(description).map(|(c, d)| { if &d == "off" { quote! {} @@ -207,7 +223,9 @@ fn get_enum_data(input: &DeriveInput) -> Result<&syn::DataEnum, TokenStream> { } } -fn parse_attributes(input: &[syn::Attribute]) -> 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::() { From 6e91f7f79641bb6cf899964ded9c0c3e14190165 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 25 Jul 2020 16:27:39 +0300 Subject: [PATCH 055/755] removed suffix in generated code --- src/fields_parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fields_parse.rs b/src/fields_parse.rs index dffaf9f9..c8bdc7c6 100644 --- a/src/fields_parse.rs +++ b/src/fields_parse.rs @@ -55,7 +55,7 @@ pub fn impl_parse_args_named( data.named.iter().map(|f| &f.ty), data.named.len(), ); - let i = 0..data.named.len(); + let i = (0..data.named.len()).map(|u| syn::Index::from(u)); let name = data.named.iter().map(|f| f.ident.as_ref().unwrap()); let res = quote! { { From a934061fb537a9bbdb9cb608f133efc20bd85ae3 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 25 Jul 2020 16:55:35 +0300 Subject: [PATCH 056/755] removed suffix in generated code (2) --- src/fields_parse.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fields_parse.rs b/src/fields_parse.rs index c8bdc7c6..508ff383 100644 --- a/src/fields_parse.rs +++ b/src/fields_parse.rs @@ -31,7 +31,7 @@ pub fn impl_parse_args_unnamed( data.unnamed.iter().map(|f| &f.ty), data.unnamed.len(), ); - let iter = 0..data.unnamed.len(); + let iter = (0..data.unnamed.len()).map( syn::Index::from); let mut initialization = quote! {}; for i in iter { initialization.extend(quote! { arguments.#i, }) @@ -55,7 +55,7 @@ pub fn impl_parse_args_named( data.named.iter().map(|f| &f.ty), data.named.len(), ); - let i = (0..data.named.len()).map(|u| syn::Index::from(u)); + let i = (0..data.named.len()).map(syn::Index::from); let name = data.named.iter().map(|f| f.ident.as_ref().unwrap()); let res = quote! { { From 055a4d09cb75fd7c04bdc5315ce80d1d29314242 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 25 Jul 2020 16:57:51 +0300 Subject: [PATCH 057/755] fmt --- src/fields_parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fields_parse.rs b/src/fields_parse.rs index 508ff383..10b775fc 100644 --- a/src/fields_parse.rs +++ b/src/fields_parse.rs @@ -31,7 +31,7 @@ pub fn impl_parse_args_unnamed( data.unnamed.iter().map(|f| &f.ty), data.unnamed.len(), ); - let iter = (0..data.unnamed.len()).map( syn::Index::from); + let iter = (0..data.unnamed.len()).map(syn::Index::from); let mut initialization = quote! {}; for i in iter { initialization.extend(quote! { arguments.#i, }) From 2c17d2f7a3098598e5d8ec88bd102ef6a3a50238 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 25 Jul 2020 22:33:56 +0600 Subject: [PATCH 058/755] Implement a nicer approach with transitions --- CHANGELOG.md | 2 +- Cargo.toml | 3 +- src/lib.rs | 88 +++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 70 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49359c8f..bdf726b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] ### Added - - `#[derive(BotDialogue)]` with `#[transition( TokenStream { +#[proc_macro_attribute] +pub fn teloxide(attr: TokenStream, item: TokenStream) -> TokenStream { + match attr.to_string().as_ref() { + "transition" => { + let item_cloned = item.clone(); + let input = parse_macro_input!(item as ItemFn); + let params = input.sig.inputs.iter().collect::>(); + + if params.len() != 2 { + panic!( + "An transition function must accept two parameters: a \ + state type and TransitionIn" + ); + } + + // This is actually used inside the quite! { ... } below. + #[allow(unused_variables)] + let state_type = match params[0] { + FnArg::Typed(pat_type) => &pat_type.ty, + _ => unreachable!(), + }; + let fn_name = input.sig.ident; + let fn_return_type = match input.sig.output { + ReturnType::Type(_arrow, _type) => _type, + _ => panic!( + "A subtransition must return TransitionOut" + ), + }; + let item = proc_macro2::TokenStream::from(item_cloned); + + let impl_transition = quote! { + impl teloxide::dispatching::dialogue::SubTransition< + <#fn_return_type as teloxide::dispatching::dialogue::SubTransitionOutputType>::Output> + for #state_type { + fn react(self, cx: teloxide::dispatching::dialogue::TransitionIn) + -> futures::future::BoxFuture<'static, #fn_return_type> { + #item + + futures::future::FutureExt::boxed(#fn_name(self, cx)) + } + } + }; + + impl_transition.into() + } + _ => { + panic!("Unrecognised attribute '{}'", attr); + } + } +} + +#[proc_macro_derive(Transition)] +pub fn derive_transition(item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemEnum); let mut dispatch_fn = "".to_owned(); write!( dispatch_fn, - "impl teloxide::dispatching::dialogue::BotDialogue for {} {{ fn \ - dispatch(self, cx: teloxide::dispatching::dialogue::TransitionIn) -> \ + "impl teloxide::dispatching::dialogue::Transition for {} {{ fn \ + react(self, cx: teloxide::dispatching::dialogue::TransitionIn) -> \ futures::future::BoxFuture<'static, \ teloxide::dispatching::dialogue::TransitionOut> {{ \ futures::future::FutureExt::boxed(async {{ match self {{", @@ -38,22 +92,14 @@ pub fn derive_bot_dialogue(item: TokenStream) -> TokenStream { .unwrap(); for variant in input.variants.iter() { - if let Some(handler) = - variant.attrs.iter().find(|attr| match attr.path.get_ident() { - Some(ident) => ident == "transition", - None => false, - }) - { - let mut handler_fn = handler.tokens.to_string()[1..].to_owned(); - handler_fn.pop(); - - write!( - dispatch_fn, - "{}::{}(state) => {}(cx, state).await,", - input.ident, variant.ident, handler_fn - ) - .unwrap(); - } + write!( + dispatch_fn, + "{}::{}(state) => \ + teloxide::dispatching::dialogue::SubTransition::react(state, \ + cx).await,", + input.ident, variant.ident + ) + .unwrap(); } write!(dispatch_fn, "}} }}) }} }}").unwrap(); From 117690514d9b50ac34ef130c4f6253b183cc4528 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sun, 26 Jul 2020 22:53:37 +0600 Subject: [PATCH 059/755] Respect an auxiliary parameter --- src/lib.rs | 63 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d6ce22ec..30583ba8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,8 @@ use crate::{ use proc_macro::TokenStream; use quote::{quote, ToTokens}; use syn::{ - parse_macro_input, DeriveInput, Fields, FnArg, ItemEnum, ItemFn, ReturnType, + parse_macro_input, DeriveInput, Fields, FnArg, ItemEnum, ItemFn, + ReturnType, Type, }; use std::fmt::Write; @@ -31,10 +32,10 @@ pub fn teloxide(attr: TokenStream, item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemFn); let params = input.sig.inputs.iter().collect::>(); - if params.len() != 2 { + if params.len() != 2 && params.len() != 3 { panic!( - "An transition function must accept two parameters: a \ - state type and TransitionIn" + "An transition function must accept two/three parameters: \ + a state type, TransitionIn, and an optional data." ); } @@ -52,17 +53,34 @@ pub fn teloxide(attr: TokenStream, item: TokenStream) -> TokenStream { type>" ), }; + let aux_param_type = match params.get(2) { + Some(data_param_type) => match *data_param_type { + FnArg::Typed(typed) => typed.ty.clone(), + _ => unreachable!(), + }, + None => { + let unit_type = proc_macro::TokenStream::from(quote! {()}); + Box::new(parse_macro_input!(unit_type as Type)) + } + }; + let call_fn = match params.get(2) { + Some(_) => { + quote! { #fn_name(self, cx, aux) } + } + None => quote! { #fn_name(self, cx) }, + }; + let item = proc_macro2::TokenStream::from(item_cloned); let impl_transition = quote! { - impl teloxide::dispatching::dialogue::SubTransition< - <#fn_return_type as teloxide::dispatching::dialogue::SubTransitionOutputType>::Output> - for #state_type { - fn react(self, cx: teloxide::dispatching::dialogue::TransitionIn) + impl teloxide::dispatching::dialogue::SubTransition for #state_type { + type Aux = #aux_param_type; + type Dialogue = <#fn_return_type as teloxide::dispatching::dialogue::SubTransitionOutputType>::Output; + + fn react(self, cx: teloxide::dispatching::dialogue::TransitionIn, aux: #aux_param_type) -> futures::future::BoxFuture<'static, #fn_return_type> { #item - - futures::future::FutureExt::boxed(#fn_name(self, cx)) + futures::future::FutureExt::boxed(#call_fn) } } }; @@ -82,11 +100,28 @@ pub fn derive_transition(item: TokenStream) -> TokenStream { write!( dispatch_fn, - "impl teloxide::dispatching::dialogue::Transition for {} {{ fn \ - react(self, cx: teloxide::dispatching::dialogue::TransitionIn) -> \ + "impl teloxide::dispatching::dialogue::Transition<<{0} as \ + teloxide::dispatching::dialogue::SubTransition>::Aux> for {1} {{ fn \ + react(self, cx: teloxide::dispatching::dialogue::TransitionIn, aux: \ + <{0} as teloxide::dispatching::dialogue::SubTransition>::Aux) -> \ futures::future::BoxFuture<'static, \ teloxide::dispatching::dialogue::TransitionOut> {{ \ futures::future::FutureExt::boxed(async {{ match self {{", + // .unwrap() because empty enumerations are not yet allowed in stable + // Rust. + match &input.variants.iter().next().unwrap().fields { + Fields::Unnamed(fields) => { + fields + .unnamed + .iter() + .next() + .unwrap() + .ty + .to_token_stream() + .to_string() + } + _ => panic!("Only one unnamed field per variant is allowed"), + }, input.ident ) .unwrap(); @@ -95,8 +130,8 @@ pub fn derive_transition(item: TokenStream) -> TokenStream { write!( dispatch_fn, "{}::{}(state) => \ - teloxide::dispatching::dialogue::SubTransition::react(state, \ - cx).await,", + teloxide::dispatching::dialogue::SubTransition::react(state, cx, \ + aux).await,", input.ident, variant.ident ) .unwrap(); From 58c71b523b7ac0ba7ea2a97185b4be8fe0c9af86 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sun, 26 Jul 2020 23:01:15 +0600 Subject: [PATCH 060/755] Refactor a little bit --- src/lib.rs | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 30583ba8..379746b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,6 +98,24 @@ pub fn derive_transition(item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemEnum); let mut dispatch_fn = "".to_owned(); + let enum_name = input.ident; + let field_type_of_first_variant = + match &input.variants.iter().next().unwrap().fields { + Fields::Unnamed(fields) => { + fields + .unnamed + .iter() + .next() + // .unwrap() because empty enumerations are not yet allowed + // in stable Rust. + .unwrap() + .ty + .to_token_stream() + .to_string() + } + _ => panic!("Only one unnamed field per variant is allowed"), + }; + write!( dispatch_fn, "impl teloxide::dispatching::dialogue::Transition<<{0} as \ @@ -107,22 +125,7 @@ pub fn derive_transition(item: TokenStream) -> TokenStream { futures::future::BoxFuture<'static, \ teloxide::dispatching::dialogue::TransitionOut> {{ \ futures::future::FutureExt::boxed(async {{ match self {{", - // .unwrap() because empty enumerations are not yet allowed in stable - // Rust. - match &input.variants.iter().next().unwrap().fields { - Fields::Unnamed(fields) => { - fields - .unnamed - .iter() - .next() - .unwrap() - .ty - .to_token_stream() - .to_string() - } - _ => panic!("Only one unnamed field per variant is allowed"), - }, - input.ident + field_type_of_first_variant, enum_name ) .unwrap(); @@ -132,7 +135,7 @@ pub fn derive_transition(item: TokenStream) -> TokenStream { "{}::{}(state) => \ teloxide::dispatching::dialogue::SubTransition::react(state, cx, \ aux).await,", - input.ident, variant.ident + enum_name, variant.ident ) .unwrap(); } From a21513353ea8e9b5ea03eaa1e62963c20e4ceddd Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sun, 26 Jul 2020 23:17:41 +0600 Subject: [PATCH 061/755] Fix Clippy --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 379746b3..c01519c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -166,8 +166,7 @@ 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(|variant| variant).collect(); + let variants: Vec<&syn::Variant> = data_enum.variants.iter().collect(); let mut variant_infos = vec![]; for variant in variants.iter() { From cdd988afe9d7339ad5d3eacb0916366ba17447e9 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 27 Jul 2020 00:13:00 +0600 Subject: [PATCH 062/755] async { ... } -> async move { ... } --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c01519c0..c7d516b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,7 +124,7 @@ pub fn derive_transition(item: TokenStream) -> TokenStream { <{0} as teloxide::dispatching::dialogue::SubTransition>::Aux) -> \ futures::future::BoxFuture<'static, \ teloxide::dispatching::dialogue::TransitionOut> {{ \ - futures::future::FutureExt::boxed(async {{ match self {{", + futures::future::FutureExt::boxed(async move {{ match self {{", field_type_of_first_variant, enum_name ) .unwrap(); From 5f89c46b397581164fb9cd9b825094e082ec2869 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 27 Jul 2020 00:25:56 +0600 Subject: [PATCH 063/755] Respect type Aux in Transition --- src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c7d516b7..0f6675f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,11 +118,10 @@ pub fn derive_transition(item: TokenStream) -> TokenStream { write!( dispatch_fn, - "impl teloxide::dispatching::dialogue::Transition<<{0} as \ - teloxide::dispatching::dialogue::SubTransition>::Aux> for {1} {{ fn \ + "impl teloxide::dispatching::dialogue::Transition for {1} {{type Aux \ + = <{0} as teloxide::dispatching::dialogue::SubTransition>::Aux;fn \ react(self, cx: teloxide::dispatching::dialogue::TransitionIn, aux: \ - <{0} as teloxide::dispatching::dialogue::SubTransition>::Aux) -> \ - futures::future::BoxFuture<'static, \ + Self::Aux) -> futures::future::BoxFuture<'static, \ teloxide::dispatching::dialogue::TransitionOut> {{ \ futures::future::FutureExt::boxed(async move {{ match self {{", field_type_of_first_variant, enum_name From 7a755bfc2f698443af990b8b117b73ebe16362d1 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 27 Jul 2020 00:45:12 +0600 Subject: [PATCH 064/755] Document #[derive(Transition)] and #[teloxide(transition)] --- src/lib.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 0f6675f1..358c3bc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,30 @@ use syn::{ use std::fmt::Write; +/// The docs is below. +/// +/// The only accepted form at the current moment is `#[teloxide(transition)]` on +/// an asynchronous function. Either this: +/// +/// ```no_compile +/// #[teloxide(transition)] +/// async fn my_transition(state: MyState, cx: TransitionIn, ans: T) -> TransitionOut { +/// todo!() +/// } +/// ``` +/// +/// Or this: +/// +/// ```no_compile +/// #[teloxide(transition)] +/// async fn my_transition(state: MyState, cx: TransitionIn) -> TransitionOut { +/// todo!() +/// } +/// ``` +/// +/// Notice the presence/absence of `ans: T`. In the first case, it generates +/// `impl SubTransition for MyState { type Aux = T; type Dialogue = MyDialogue; +/// ... }`. In the second case, the `Aux` type defaults to `()`. #[proc_macro_attribute] pub fn teloxide(attr: TokenStream, item: TokenStream) -> TokenStream { match attr.to_string().as_ref() { @@ -93,6 +117,12 @@ pub fn teloxide(attr: TokenStream, item: TokenStream) -> TokenStream { } } +/// The docs is below. +/// +/// All the variants must be of the form `VariantName(MyStateType)`, and +/// `MyStateType` must implement `SubTransition`. All `MyStateType`s must have +/// the same `SubTransition::Aux`, which will be also used in the generated +/// implementation. #[proc_macro_derive(Transition)] pub fn derive_transition(item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemEnum); From 2ae87a76ae072f4a5c01d5cac9e83cafbb6f69d9 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 27 Jul 2020 00:49:40 +0600 Subject: [PATCH 065/755] SubTransition -> Subtransition --- src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 358c3bc7..00f7eee7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,9 +97,9 @@ pub fn teloxide(attr: TokenStream, item: TokenStream) -> TokenStream { let item = proc_macro2::TokenStream::from(item_cloned); let impl_transition = quote! { - impl teloxide::dispatching::dialogue::SubTransition for #state_type { + impl teloxide::dispatching::dialogue::Subtransition for #state_type { type Aux = #aux_param_type; - type Dialogue = <#fn_return_type as teloxide::dispatching::dialogue::SubTransitionOutputType>::Output; + type Dialogue = <#fn_return_type as teloxide::dispatching::dialogue::SubtransitionOutputType>::Output; fn react(self, cx: teloxide::dispatching::dialogue::TransitionIn, aux: #aux_param_type) -> futures::future::BoxFuture<'static, #fn_return_type> { @@ -120,8 +120,8 @@ pub fn teloxide(attr: TokenStream, item: TokenStream) -> TokenStream { /// The docs is below. /// /// All the variants must be of the form `VariantName(MyStateType)`, and -/// `MyStateType` must implement `SubTransition`. All `MyStateType`s must have -/// the same `SubTransition::Aux`, which will be also used in the generated +/// `MyStateType` must implement `Subtransition`. All `MyStateType`s must have +/// the same `Subtransition::Aux`, which will be also used in the generated /// implementation. #[proc_macro_derive(Transition)] pub fn derive_transition(item: TokenStream) -> TokenStream { @@ -149,7 +149,7 @@ pub fn derive_transition(item: TokenStream) -> TokenStream { write!( dispatch_fn, "impl teloxide::dispatching::dialogue::Transition for {1} {{type Aux \ - = <{0} as teloxide::dispatching::dialogue::SubTransition>::Aux;fn \ + = <{0} as teloxide::dispatching::dialogue::Subtransition>::Aux;fn \ react(self, cx: teloxide::dispatching::dialogue::TransitionIn, aux: \ Self::Aux) -> futures::future::BoxFuture<'static, \ teloxide::dispatching::dialogue::TransitionOut> {{ \ @@ -162,7 +162,7 @@ pub fn derive_transition(item: TokenStream) -> TokenStream { write!( dispatch_fn, "{}::{}(state) => \ - teloxide::dispatching::dialogue::SubTransition::react(state, cx, \ + teloxide::dispatching::dialogue::Subtransition::react(state, cx, \ aux).await,", enum_name, variant.ident ) From e2e0c838a57c86086ea6577fde4fab238f83b45e Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 27 Jul 2020 00:51:02 +0600 Subject: [PATCH 066/755] #[teloxide(transition)] -> #[teloxide(subtransition)] --- CHANGELOG.md | 2 +- src/lib.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdf726b3..c7a6863c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] ### Added - - `#[derive(Transition)]` with `#[teloxide(transition)]`. + - `#[derive(Transition)]` with `#[teloxide(subtransition)]`. ### Removed - The `dev` branch. diff --git a/src/lib.rs b/src/lib.rs index 00f7eee7..68fcf6cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,11 +26,11 @@ use std::fmt::Write; /// The docs is below. /// -/// The only accepted form at the current moment is `#[teloxide(transition)]` on -/// an asynchronous function. Either this: +/// The only accepted form at the current moment is `#[teloxide(subtransition)]` +/// on an asynchronous function. Either this: /// /// ```no_compile -/// #[teloxide(transition)] +/// #[teloxide(subtransition)] /// async fn my_transition(state: MyState, cx: TransitionIn, ans: T) -> TransitionOut { /// todo!() /// } @@ -39,7 +39,7 @@ use std::fmt::Write; /// Or this: /// /// ```no_compile -/// #[teloxide(transition)] +/// #[teloxide(subtransition)] /// async fn my_transition(state: MyState, cx: TransitionIn) -> TransitionOut { /// todo!() /// } @@ -51,7 +51,7 @@ use std::fmt::Write; #[proc_macro_attribute] pub fn teloxide(attr: TokenStream, item: TokenStream) -> TokenStream { match attr.to_string().as_ref() { - "transition" => { + "subtransition" => { let item_cloned = item.clone(); let input = parse_macro_input!(item as ItemFn); let params = input.sig.inputs.iter().collect::>(); From 37c32743a850686a2cacc5748894e989dc9a0311 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 27 Jul 2020 00:58:08 +0600 Subject: [PATCH 067/755] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7a6863c..eceacff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [unreleased] +## [0.3.2] - 2020-07-27 ### Added - `#[derive(Transition)]` with `#[teloxide(subtransition)]`. From 36f83f1093572f9eb2d89c0ac3cc14c3167de6b1 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 27 Jul 2020 00:58:32 +0600 Subject: [PATCH 068/755] Update Cargo.toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2cf0f842..3e294160 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teloxide-macros" -version = "0.3.1" +version = "0.3.2" description = "The teloxide's procedural macros" authors = ["p0lunin "] license = "MIT" From 0d6ede03a931bd945cc6055fcce7e6c878d8c0b4 Mon Sep 17 00:00:00 2001 From: Kirill Mironov Date: Fri, 31 Jul 2020 23:56:32 +0300 Subject: [PATCH 069/755] Unpair TransitionOut from concrete Error type --- src/lib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 68fcf6cd..587cea0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,6 +100,7 @@ pub fn teloxide(attr: TokenStream, item: TokenStream) -> TokenStream { impl teloxide::dispatching::dialogue::Subtransition for #state_type { type Aux = #aux_param_type; type Dialogue = <#fn_return_type as teloxide::dispatching::dialogue::SubtransitionOutputType>::Output; + type Error = <#fn_return_type as teloxide::dispatching::dialogue::SubtransitionOutputType>::Error; fn react(self, cx: teloxide::dispatching::dialogue::TransitionIn, aux: #aux_param_type) -> futures::future::BoxFuture<'static, #fn_return_type> { @@ -149,11 +150,13 @@ pub fn derive_transition(item: TokenStream) -> TokenStream { write!( dispatch_fn, "impl teloxide::dispatching::dialogue::Transition for {1} {{type Aux \ - = <{0} as teloxide::dispatching::dialogue::Subtransition>::Aux;fn \ + = <{0} as teloxide::dispatching::dialogue::Subtransition>::Aux;type \ + Error = <{0} as \ + teloxide::dispatching::dialogue::Subtransition>::Error;fn \ react(self, cx: teloxide::dispatching::dialogue::TransitionIn, aux: \ Self::Aux) -> futures::future::BoxFuture<'static, \ - teloxide::dispatching::dialogue::TransitionOut> {{ \ - futures::future::FutureExt::boxed(async move {{ match self {{", + teloxide::dispatching::dialogue::TransitionOut> \ + {{ futures::future::FutureExt::boxed(async move {{ match self {{", field_type_of_first_variant, enum_name ) .unwrap(); From 30024d7792b27e1bf9fcdb61c6fe8555d6f0ee2a Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 1 Aug 2020 17:49:14 +0600 Subject: [PATCH 070/755] Update the docs --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 587cea0e..5744c436 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -122,8 +122,8 @@ pub fn teloxide(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// All the variants must be of the form `VariantName(MyStateType)`, and /// `MyStateType` must implement `Subtransition`. All `MyStateType`s must have -/// the same `Subtransition::Aux`, which will be also used in the generated -/// implementation. +/// the same `Subtransition::Aux` and `Subtransition::Error`, which will be also +/// used in the generated implementation. #[proc_macro_derive(Transition)] pub fn derive_transition(item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemEnum); From 4b683d7c90b5a5d1c6d73ee3ed7dfefe3bf65e16 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 6 Aug 2020 16:40:41 +0300 Subject: [PATCH 071/755] Initial commit --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..84a1b0b0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 teloxide + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 7891fa02bf6f5164d2cf0ae3ec96a8f7695c7e29 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 12 Aug 2020 18:04:50 +0300 Subject: [PATCH 072/755] Initial commit --- .github/workflows/ci.yml | 86 ++++++++++++++++++++++++++++++++++++++++ .gitignore | 2 + Cargo.toml | 26 ++++++++++++ README.md | 3 ++ netlify.toml | 12 ++++++ src/lib.rs | 12 ++++++ 6 files changed, 141 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 netlify.toml create mode 100644 src/lib.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..4790ad17 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,86 @@ +on: + push: + branches: + - master + pull_request: + branches: + - master + +name: Continuous integration + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + - beta + - nightly + + include: + - rust: stable + features: "" + - rust: beta + features: "" + - rust: nightly + features: "--all-features" + + steps: + - uses: actions/checkout@v1 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + + - name: build + uses: actions-rs/cargo@v1 + with: + command: build + args: --verbose ${{ matrix.features }} + + - name: test + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose ${{ matrix.features }} + + clippy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + components: clippy + + - name: clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --all-targets --all-features -- -D warnings + + style: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + components: rustfmt + + - name: fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..96ef6c0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..57b4c75c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "teloxide-core" +version = "0.1.0" +edition = "2018" +authors = [ + "Temirkhan Myrzamadi ", + "Waffle Lapkin ", + "p0lunin ", + "Mishko torop'izhko", + "Mr-Andersen", + "Sergey Levitin ", + "Rustem B. ", + "Alexey Fedechkin " +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[features] +# features those require nightly compiler +nightly = [] + +[package.metadata."docs.rs"] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/README.md b/README.md new file mode 100644 index 00000000..8026aea2 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# teloxide-core + +Core part of `teloxide` library. \ No newline at end of file diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 00000000..ecb9c8fb --- /dev/null +++ b/netlify.toml @@ -0,0 +1,12 @@ +[build] +# Directory (relative to root of your repo) that contains the deploy-ready +# HTML files and assets generated by the build. If a base directory has +# been specified, include it in the publish directory path. +publish = "target/doc" + +# Default build command. +command = 'curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly --profile minimal && source $HOME/.cargo/env && RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --no-deps --all-features' + +[[redirects]] + from = "/*" + to = "/teloxide_core" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..1adb08cb --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,12 @@ +//! Core part of `teloxide` library. +// TODO: expand docs + +// we pass "--cfg docsrs" when building docs to add `This is supported on feature="..." only.` +// +// To properly build docs of this crate run +// ```console +// $ RUSTDOCFLAGS="--cfg docsrs" cargo doc --open --all-features +// ``` +#![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg))] +#![forbid(unsafe_code)] +#![deny(missing_docs)] From a70cb4e00861b40d3409b1eea68a9ec4e87f3a16 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 12 Aug 2020 19:39:40 +0300 Subject: [PATCH 073/755] Move core code from teloxide --- CHANGELOG.md | 25 + Cargo.toml | 14 + rustfmt.toml | 6 + src/bot/api.rs | 1691 ++++++++++++++++ src/bot/download.rs | 65 + src/bot/mod.rs | 240 +++ src/errors.rs | 515 +++++ src/lib.rs | 43 +- src/net/download.rs | 49 + src/net/mod.rs | 61 + src/net/request.rs | 63 + src/net/telegram_response.rs | 70 + src/requests/all/add_sticker_to_set.rs | 109 + src/requests/all/answer_callback_query.rs | 101 + src/requests/all/answer_inline_query.rs | 146 ++ src/requests/all/answer_pre_checkout_query.rs | 82 + src/requests/all/answer_shipping_query.rs | 86 + src/requests/all/create_new_sticker_set.rs | 134 ++ src/requests/all/delete_chat_photo.rs | 50 + src/requests/all/delete_chat_sticker_set.rs | 55 + src/requests/all/delete_message.rs | 67 + src/requests/all/delete_sticker_from_set.rs | 47 + src/requests/all/delete_webhook.rs | 37 + .../all/edit_inline_message_caption.rs | 83 + .../all/edit_inline_message_live_location.rs | 77 + src/requests/all/edit_inline_message_media.rs | 78 + .../all/edit_inline_message_reply_markup.rs | 62 + src/requests/all/edit_inline_message_text.rs | 98 + src/requests/all/edit_message_caption.rs | 91 + .../all/edit_message_live_location.rs | 91 + src/requests/all/edit_message_media.rs | 85 + src/requests/all/edit_message_reply_markup.rs | 69 + src/requests/all/edit_message_text.rs | 107 + src/requests/all/export_chat_invite_link.rs | 65 + src/requests/all/forward_message.rs | 81 + src/requests/all/get_chat.rs | 50 + src/requests/all/get_chat_administrators.rs | 53 + src/requests/all/get_chat_member.rs | 55 + src/requests/all/get_chat_members_count.rs | 48 + src/requests/all/get_file.rs | 62 + src/requests/all/get_game_high_scores.rs | 63 + src/requests/all/get_me.rs | 33 + src/requests/all/get_my_commands.rs | 33 + src/requests/all/get_sticker_set.rs | 47 + src/requests/all/get_updates.rs | 108 + src/requests/all/get_user_profile_photos.rs | 58 + src/requests/all/get_webhook_info.rs | 38 + src/requests/all/kick_chat_member.rs | 72 + src/requests/all/leave_chat.rs | 48 + src/requests/all/mod.rs | 152 ++ src/requests/all/pin_chat_message.rs | 69 + src/requests/all/promote_chat_member.rs | 134 ++ src/requests/all/restrict_chat_member.rs | 76 + src/requests/all/send_animation.rs | 175 ++ src/requests/all/send_audio.rs | 200 ++ src/requests/all/send_chat_action.rs | 105 + src/requests/all/send_contact.rs | 129 ++ src/requests/all/send_dice.rs | 94 + src/requests/all/send_document.rs | 154 ++ src/requests/all/send_game.rs | 92 + src/requests/all/send_invoice.rs | 299 +++ src/requests/all/send_location.rs | 110 ++ src/requests/all/send_media_group.rs | 85 + src/requests/all/send_message.rs | 121 ++ src/requests/all/send_photo.rs | 138 ++ src/requests/all/send_poll.rs | 217 ++ src/requests/all/send_sticker.rs | 113 ++ src/requests/all/send_venue.rs | 159 ++ src/requests/all/send_video.rs | 200 ++ src/requests/all/send_video_note.rs | 152 ++ src/requests/all/send_voice.rs | 156 ++ .../set_chat_administrator_custom_title.rs | 75 + src/requests/all/set_chat_description.rs | 62 + src/requests/all/set_chat_permissions.rs | 58 + src/requests/all/set_chat_photo.rs | 58 + src/requests/all/set_chat_sticker_set.rs | 64 + src/requests/all/set_chat_title.rs | 63 + src/requests/all/set_game_score.rs | 89 + src/requests/all/set_my_commands.rs | 50 + .../all/set_sticker_position_in_set.rs | 56 + src/requests/all/set_sticker_set_thumb.rs | 73 + src/requests/all/set_webhook.rs | 114 ++ .../all/stop_inline_message_live_location.rs | 62 + .../all/stop_message_live_location.rs | 70 + src/requests/all/stop_poll.rs | 66 + src/requests/all/unban_chat_member.rs | 58 + src/requests/all/unpin_chat_message.rs | 52 + src/requests/all/upload_sticker_file.rs | 55 + src/requests/form_builder.rs | 156 ++ src/requests/mod.rs | 32 + src/requests/utils.rs | 31 + src/types/allowed_update.rs | 14 + src/types/animation.rs | 165 ++ src/types/audio.rs | 152 ++ src/types/bot_command.rs | 42 + src/types/callback_game.rs | 13 + src/types/callback_query.rs | 158 ++ src/types/chat.rs | 353 ++++ src/types/chat_action.rs | 17 + src/types/chat_id.rs | 39 + src/types/chat_member.rs | 262 +++ src/types/chat_permissions.rs | 48 + src/types/chat_photo.rs | 82 + src/types/chosen_inline_result.rs | 85 + src/types/contact.rs | 79 + src/types/dice.rs | 38 + src/types/dice_emoji.rs | 16 + src/types/document.rs | 92 + src/types/encrypted_credentials.rs | 94 + src/types/encrypted_passport_element.rs | 794 ++++++++ src/types/file.rs | 75 + src/types/force_reply.rs | 41 + src/types/game.rs | 106 + src/types/game_high_score.rs | 40 + src/types/inline_keyboard_button.rs | 135 ++ src/types/inline_keyboard_markup.rs | 109 + src/types/inline_query.rs | 74 + src/types/inline_query_result.rs | 102 + src/types/inline_query_result_article.rs | 128 ++ src/types/inline_query_result_audio.rs | 133 ++ src/types/inline_query_result_cached_audio.rs | 96 + .../inline_query_result_cached_document.rs | 121 ++ src/types/inline_query_result_cached_gif.rs | 109 + .../inline_query_result_cached_mpeg4_gif.rs | 101 + src/types/inline_query_result_cached_photo.rs | 120 ++ .../inline_query_result_cached_sticker.rs | 70 + src/types/inline_query_result_cached_video.rs | 121 ++ src/types/inline_query_result_cached_voice.rs | 109 + src/types/inline_query_result_contact.rs | 140 ++ src/types/inline_query_result_document.rs | 138 ++ src/types/inline_query_result_game.rs | 55 + src/types/inline_query_result_gif.rs | 148 ++ src/types/inline_query_result_location.rs | 128 ++ src/types/inline_query_result_mpeg4_gif.rs | 149 ++ src/types/inline_query_result_photo.rs | 152 ++ src/types/inline_query_result_venue.rs | 157 ++ src/types/inline_query_result_video.rs | 182 ++ src/types/inline_query_result_voice.rs | 119 ++ src/types/input_file.rs | 101 + src/types/input_media.rs | 511 +++++ src/types/input_message_content.rs | 318 +++ src/types/invoice.rs | 91 + src/types/keyboard_button.rs | 156 ++ src/types/keyboard_button_poll_type.rs | 24 + src/types/label_price.rs | 56 + src/types/location.rs | 28 + src/types/login_url.rs | 54 + src/types/mask_position.rs | 58 + src/types/me.rs | 57 + src/types/message.rs | 1753 +++++++++++++++++ src/types/message_entity.rs | 162 ++ src/types/mod.rs | 194 ++ src/types/non_telegram_types/country_code.rs | 254 +++ src/types/non_telegram_types/currency.rs | 89 + src/types/non_telegram_types/mime_wrapper.rs | 44 + src/types/non_telegram_types/mod.rs | 7 + src/types/order_info.rs | 72 + src/types/parse_mode.rs | 189 ++ src/types/passport_data.rs | 40 + src/types/passport_element_error.rs | 558 ++++++ src/types/passport_file.rs | 66 + src/types/photo_size.rs | 94 + src/types/poll.rs | 250 +++ src/types/poll_answer.rs | 48 + src/types/poll_type.rs | 9 + src/types/pre_checkout_query.rs | 108 + src/types/reply_keyboard_markup.rs | 98 + src/types/reply_keyboard_remove.rs | 52 + src/types/reply_markup.rs | 27 + src/types/response_parameters.rs | 43 + src/types/send_invoice.rs | 229 +++ src/types/shipping_address.rs | 100 + src/types/shipping_option.rs | 71 + src/types/shipping_query.rs | 63 + src/types/sticker.rs | 135 ++ src/types/sticker_set.rs | 89 + src/types/sticker_type.rs | 26 + src/types/successful_payment.rs | 112 ++ src/types/target_message.rs | 17 + src/types/unit_false.rs | 76 + src/types/unit_true.rs | 79 + src/types/update.rs | 299 +++ src/types/user.rs | 132 ++ src/types/user_profile_photos.rs | 40 + src/types/venue.rs | 74 + src/types/video.rs | 108 + src/types/video_note.rs | 89 + src/types/voice.rs | 74 + src/types/webhook_info.rs | 98 + 189 files changed, 23207 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md create mode 100644 rustfmt.toml create mode 100644 src/bot/api.rs create mode 100644 src/bot/download.rs create mode 100644 src/bot/mod.rs create mode 100644 src/errors.rs create mode 100644 src/net/download.rs create mode 100644 src/net/mod.rs create mode 100644 src/net/request.rs create mode 100644 src/net/telegram_response.rs create mode 100644 src/requests/all/add_sticker_to_set.rs create mode 100644 src/requests/all/answer_callback_query.rs create mode 100644 src/requests/all/answer_inline_query.rs create mode 100644 src/requests/all/answer_pre_checkout_query.rs create mode 100644 src/requests/all/answer_shipping_query.rs create mode 100644 src/requests/all/create_new_sticker_set.rs create mode 100644 src/requests/all/delete_chat_photo.rs create mode 100644 src/requests/all/delete_chat_sticker_set.rs create mode 100644 src/requests/all/delete_message.rs create mode 100644 src/requests/all/delete_sticker_from_set.rs create mode 100644 src/requests/all/delete_webhook.rs create mode 100644 src/requests/all/edit_inline_message_caption.rs create mode 100644 src/requests/all/edit_inline_message_live_location.rs create mode 100644 src/requests/all/edit_inline_message_media.rs create mode 100644 src/requests/all/edit_inline_message_reply_markup.rs create mode 100644 src/requests/all/edit_inline_message_text.rs create mode 100644 src/requests/all/edit_message_caption.rs create mode 100644 src/requests/all/edit_message_live_location.rs create mode 100644 src/requests/all/edit_message_media.rs create mode 100644 src/requests/all/edit_message_reply_markup.rs create mode 100644 src/requests/all/edit_message_text.rs create mode 100644 src/requests/all/export_chat_invite_link.rs create mode 100644 src/requests/all/forward_message.rs create mode 100644 src/requests/all/get_chat.rs create mode 100644 src/requests/all/get_chat_administrators.rs create mode 100644 src/requests/all/get_chat_member.rs create mode 100644 src/requests/all/get_chat_members_count.rs create mode 100644 src/requests/all/get_file.rs create mode 100644 src/requests/all/get_game_high_scores.rs create mode 100644 src/requests/all/get_me.rs create mode 100644 src/requests/all/get_my_commands.rs create mode 100644 src/requests/all/get_sticker_set.rs create mode 100644 src/requests/all/get_updates.rs create mode 100644 src/requests/all/get_user_profile_photos.rs create mode 100644 src/requests/all/get_webhook_info.rs create mode 100644 src/requests/all/kick_chat_member.rs create mode 100644 src/requests/all/leave_chat.rs create mode 100644 src/requests/all/mod.rs create mode 100644 src/requests/all/pin_chat_message.rs create mode 100644 src/requests/all/promote_chat_member.rs create mode 100644 src/requests/all/restrict_chat_member.rs create mode 100644 src/requests/all/send_animation.rs create mode 100644 src/requests/all/send_audio.rs create mode 100644 src/requests/all/send_chat_action.rs create mode 100644 src/requests/all/send_contact.rs create mode 100644 src/requests/all/send_dice.rs create mode 100644 src/requests/all/send_document.rs create mode 100644 src/requests/all/send_game.rs create mode 100644 src/requests/all/send_invoice.rs create mode 100644 src/requests/all/send_location.rs create mode 100644 src/requests/all/send_media_group.rs create mode 100644 src/requests/all/send_message.rs create mode 100644 src/requests/all/send_photo.rs create mode 100644 src/requests/all/send_poll.rs create mode 100644 src/requests/all/send_sticker.rs create mode 100644 src/requests/all/send_venue.rs create mode 100644 src/requests/all/send_video.rs create mode 100644 src/requests/all/send_video_note.rs create mode 100644 src/requests/all/send_voice.rs create mode 100644 src/requests/all/set_chat_administrator_custom_title.rs create mode 100644 src/requests/all/set_chat_description.rs create mode 100644 src/requests/all/set_chat_permissions.rs create mode 100644 src/requests/all/set_chat_photo.rs create mode 100644 src/requests/all/set_chat_sticker_set.rs create mode 100644 src/requests/all/set_chat_title.rs create mode 100644 src/requests/all/set_game_score.rs create mode 100644 src/requests/all/set_my_commands.rs create mode 100644 src/requests/all/set_sticker_position_in_set.rs create mode 100644 src/requests/all/set_sticker_set_thumb.rs create mode 100644 src/requests/all/set_webhook.rs create mode 100644 src/requests/all/stop_inline_message_live_location.rs create mode 100644 src/requests/all/stop_message_live_location.rs create mode 100644 src/requests/all/stop_poll.rs create mode 100644 src/requests/all/unban_chat_member.rs create mode 100644 src/requests/all/unpin_chat_message.rs create mode 100644 src/requests/all/upload_sticker_file.rs create mode 100644 src/requests/form_builder.rs create mode 100644 src/requests/mod.rs create mode 100644 src/requests/utils.rs create mode 100644 src/types/allowed_update.rs create mode 100644 src/types/animation.rs create mode 100644 src/types/audio.rs create mode 100644 src/types/bot_command.rs create mode 100644 src/types/callback_game.rs create mode 100644 src/types/callback_query.rs create mode 100644 src/types/chat.rs create mode 100644 src/types/chat_action.rs create mode 100644 src/types/chat_id.rs create mode 100644 src/types/chat_member.rs create mode 100644 src/types/chat_permissions.rs create mode 100644 src/types/chat_photo.rs create mode 100644 src/types/chosen_inline_result.rs create mode 100644 src/types/contact.rs create mode 100644 src/types/dice.rs create mode 100644 src/types/dice_emoji.rs create mode 100644 src/types/document.rs create mode 100644 src/types/encrypted_credentials.rs create mode 100644 src/types/encrypted_passport_element.rs create mode 100644 src/types/file.rs create mode 100644 src/types/force_reply.rs create mode 100644 src/types/game.rs create mode 100644 src/types/game_high_score.rs create mode 100644 src/types/inline_keyboard_button.rs create mode 100644 src/types/inline_keyboard_markup.rs create mode 100644 src/types/inline_query.rs create mode 100644 src/types/inline_query_result.rs create mode 100644 src/types/inline_query_result_article.rs create mode 100644 src/types/inline_query_result_audio.rs create mode 100644 src/types/inline_query_result_cached_audio.rs create mode 100644 src/types/inline_query_result_cached_document.rs create mode 100644 src/types/inline_query_result_cached_gif.rs create mode 100644 src/types/inline_query_result_cached_mpeg4_gif.rs create mode 100644 src/types/inline_query_result_cached_photo.rs create mode 100644 src/types/inline_query_result_cached_sticker.rs create mode 100644 src/types/inline_query_result_cached_video.rs create mode 100644 src/types/inline_query_result_cached_voice.rs create mode 100644 src/types/inline_query_result_contact.rs create mode 100644 src/types/inline_query_result_document.rs create mode 100644 src/types/inline_query_result_game.rs create mode 100644 src/types/inline_query_result_gif.rs create mode 100644 src/types/inline_query_result_location.rs create mode 100644 src/types/inline_query_result_mpeg4_gif.rs create mode 100644 src/types/inline_query_result_photo.rs create mode 100644 src/types/inline_query_result_venue.rs create mode 100644 src/types/inline_query_result_video.rs create mode 100644 src/types/inline_query_result_voice.rs create mode 100644 src/types/input_file.rs create mode 100644 src/types/input_media.rs create mode 100644 src/types/input_message_content.rs create mode 100644 src/types/invoice.rs create mode 100644 src/types/keyboard_button.rs create mode 100644 src/types/keyboard_button_poll_type.rs create mode 100644 src/types/label_price.rs create mode 100644 src/types/location.rs create mode 100644 src/types/login_url.rs create mode 100644 src/types/mask_position.rs create mode 100644 src/types/me.rs create mode 100644 src/types/message.rs create mode 100644 src/types/message_entity.rs create mode 100644 src/types/mod.rs create mode 100644 src/types/non_telegram_types/country_code.rs create mode 100644 src/types/non_telegram_types/currency.rs create mode 100644 src/types/non_telegram_types/mime_wrapper.rs create mode 100644 src/types/non_telegram_types/mod.rs create mode 100644 src/types/order_info.rs create mode 100644 src/types/parse_mode.rs create mode 100644 src/types/passport_data.rs create mode 100644 src/types/passport_element_error.rs create mode 100644 src/types/passport_file.rs create mode 100644 src/types/photo_size.rs create mode 100644 src/types/poll.rs create mode 100644 src/types/poll_answer.rs create mode 100644 src/types/poll_type.rs create mode 100644 src/types/pre_checkout_query.rs create mode 100644 src/types/reply_keyboard_markup.rs create mode 100644 src/types/reply_keyboard_remove.rs create mode 100644 src/types/reply_markup.rs create mode 100644 src/types/response_parameters.rs create mode 100644 src/types/send_invoice.rs create mode 100644 src/types/shipping_address.rs create mode 100644 src/types/shipping_option.rs create mode 100644 src/types/shipping_query.rs create mode 100644 src/types/sticker.rs create mode 100644 src/types/sticker_set.rs create mode 100644 src/types/sticker_type.rs create mode 100644 src/types/successful_payment.rs create mode 100644 src/types/target_message.rs create mode 100644 src/types/unit_false.rs create mode 100644 src/types/unit_true.rs create mode 100644 src/types/update.rs create mode 100644 src/types/user.rs create mode 100644 src/types/user_profile_photos.rs create mode 100644 src/types/venue.rs create mode 100644 src/types/video.rs create mode 100644 src/types/video_note.rs create mode 100644 src/types/voice.rs create mode 100644 src/types/webhook_info.rs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..738c0045 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [unreleased] + +### Added + +- Move core code here from the [`teloxide`] main repo, for older changes see it's [`CHANGELOG.md`]. + - Following modules were moved: + - `bot` + - `requests` [except `requests::respond` function] + - `types` + - `errors` + - `net` [private] + - `client_from_env` was moved from `teloxide::utils` to crate root of `teloxide-core` + - To simplify `GetUpdates` request it was changed to simply return `Vec` + (instead of `Vec>`) + + +[`teloxide`]: https://github.com/teloxide/teloxide +[`CHANGELOG.md`]: https://github.com/teloxide/teloxide/blob/master/CHANGELOG.md \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 57b4c75c..48a598d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,20 @@ authors = [ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +futures = "0.3.5" +tokio = { version = "0.2.21", features = ["fs", "stream"] } +tokio-util = "0.3.1" +bytes = "0.5.5" +async-trait = "0.1.36" +reqwest = { version = "0.10.6", features = ["json", "stream"] } + +serde = { version = "1.0.114", features = ["derive"] } +serde_json = "1.0.55" +serde_with_macros = "1.1.0" + +derive_more = "0.99.9" +mime = "0.3.16" +thiserror = "1.0.20" [features] # features those require nightly compiler diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..c61c5a92 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,6 @@ +format_code_in_doc_comments = true +wrap_comments = true +format_strings = true +merge_imports = true +use_small_heuristics = "Max" +use_field_init_shorthand = true diff --git a/src/bot/api.rs b/src/bot/api.rs new file mode 100644 index 00000000..8fe0fc80 --- /dev/null +++ b/src/bot/api.rs @@ -0,0 +1,1691 @@ +use crate::{ + requests::{ + AddStickerToSet, AnswerCallbackQuery, AnswerInlineQuery, AnswerPreCheckoutQuery, + AnswerShippingQuery, CreateNewStickerSet, DeleteChatPhoto, DeleteChatStickerSet, + DeleteMessage, DeleteStickerFromSet, DeleteWebhook, EditInlineMessageCaption, + EditInlineMessageLiveLocation, EditInlineMessageMedia, EditInlineMessageReplyMarkup, + EditInlineMessageText, EditMessageCaption, EditMessageLiveLocation, EditMessageMedia, + EditMessageReplyMarkup, EditMessageText, ExportChatInviteLink, ForwardMessage, GetChat, + GetChatAdministrators, GetChatMember, GetChatMembersCount, GetFile, GetGameHighScores, + GetMe, GetMyCommands, GetStickerSet, GetUpdates, GetUserProfilePhotos, GetWebhookInfo, + KickChatMember, LeaveChat, PinChatMessage, PromoteChatMember, RestrictChatMember, + SendAnimation, SendAudio, SendChatAction, SendChatActionKind, SendContact, SendDice, + SendDocument, SendGame, SendInvoice, SendLocation, SendMediaGroup, SendMessage, SendPhoto, + SendPoll, SendSticker, SendVenue, SendVideo, SendVideoNote, SendVoice, + SetChatAdministratorCustomTitle, SetChatDescription, SetChatPermissions, SetChatPhoto, + SetChatStickerSet, SetChatTitle, SetGameScore, SetMyCommands, SetStickerPositionInSet, + SetStickerSetThumb, SetWebhook, StopInlineMessageLiveLocation, StopMessageLiveLocation, + StopPoll, UnbanChatMember, UnpinChatMessage, UploadStickerFile, + }, + types::{ + BotCommand, ChatId, ChatPermissions, InlineQueryResult, InputFile, InputMedia, + LabeledPrice, ParseMode, StickerType, TargetMessage, + }, + Bot, +}; + +impl Bot { + /// Use this method to receive incoming updates using long polling ([wiki]). + /// + /// **Notes:** + /// 1. This method will not work if an outgoing webhook is set up. + /// 2. In order to avoid getting duplicate updates, + /// recalculate offset after each server response. + /// + /// [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.clone()) + } + + /// Use this method to specify a url and receive incoming updates via an + /// outgoing webhook. + /// + /// Whenever there is an update for the bot, we will send an + /// HTTPS POST request to the specified url, containing a JSON-serialized + /// [`Update`]. In case of an unsuccessful request, we will give up after a + /// reasonable amount of attempts. + /// + /// If you'd like to make sure that the Webhook request comes from Telegram, + /// we recommend using a secret path in the URL, e.g. + /// `https://www.example.com/`. Since nobody else knows your bot‘s + /// token, you can be pretty sure it’s us. + /// + /// [The official docs](https://core.telegram.org/bots/api#setwebhook). + /// + /// # Params + /// - `url`: HTTPS url to send updates to. + /// + /// Use an empty string to remove webhook integration. + /// + /// [`Update`]: crate::types::Update + pub fn set_webhook(&self, url: U) -> SetWebhook + where + U: Into, + { + SetWebhook::new(self.clone(), url) + } + + /// Use this method to remove webhook integration if you decide to switch + /// back to [Bot::get_updates]. + /// + /// [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.clone()) + } + + /// Use this method to get current webhook status. + /// + /// If the bot is using [`Bot::get_updates`], will return an object with the + /// url field empty. + /// + /// [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.clone()) + } + + /// 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.clone()) + } + + /// Use this method to send text messages. + /// + /// [The official docs](https://core.telegram.org/bots/api#sendmessage). + /// + /// # Params + /// - `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. + /// + /// # Notes + /// Uses [a default parse mode] if specified in [`BotBuilder`]. + /// + /// [a default parse mode]: crate::BotBuilder::parse_mode + /// [`BotBuilder`]: crate::BotBuilder + pub fn send_message(&self, chat_id: C, text: T) -> SendMessage + where + C: Into, + T: Into, + { + self.with_default_parse_mode_if_specified( + SendMessage::new(self.clone(), chat_id, text), + SendMessage::parse_mode, + ) + } + + /// Use this method to forward messages of any kind. + /// + /// [`The official docs`](https://core.telegram.org/bots/api#forwardmessage). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup or channel (in the format `@channelusername`). + /// - `from_chat_id`: Unique identifier for the chat where the original + /// message was sent (or channel username in the format + /// `@channelusername`). + /// - `message_id`: Message identifier in the chat specified in + /// [`from_chat_id`]. + /// + /// [`from_chat_id`]: ForwardMessage::from_chat_id + pub fn forward_message( + &self, + chat_id: C, + from_chat_id: F, + message_id: i32, + ) -> ForwardMessage + where + C: Into, + F: Into, + { + ForwardMessage::new(self.clone(), chat_id, from_chat_id, message_id) + } + + /// Use this method to send photos. + /// + /// [The official docs](https://core.telegram.org/bots/api#sendphoto). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup or channel (in the format `@channelusername`). + /// - `photo`: Photo to send. + /// + /// Pass [`InputFile::File`] to send a photo that exists on + /// the Telegram servers (recommended), pass an [`InputFile::Url`] for + /// Telegram to get a .webp file from the Internet, or upload a new one + /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. + /// + /// [`InputFile::File`]: crate::types::InputFile::File + /// [`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 + /// + /// # Notes + /// Uses [a default parse mode] if specified in [`BotBuilder`]. + /// + /// [a default parse mode]: crate::BotBuilder::parse_mode + /// [`BotBuilder`]: crate::BotBuilder + pub fn send_photo(&self, chat_id: C, photo: InputFile) -> SendPhoto + where + C: Into, + { + self.with_default_parse_mode_if_specified( + SendPhoto::new(self.clone(), chat_id, photo), + SendPhoto::parse_mode, + ) + } + + /// + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup or channel (in the format `@channelusername`). + /// + /// # Notes + /// Uses [a default parse mode] if specified in [`BotBuilder`]. + /// + /// [a default parse mode]: crate::BotBuilder::parse_mode + /// [`BotBuilder`]: crate::BotBuilder + pub fn send_audio(&self, chat_id: C, audio: InputFile) -> SendAudio + where + C: Into, + { + self.with_default_parse_mode_if_specified( + SendAudio::new(self.clone(), chat_id, audio), + SendAudio::parse_mode, + ) + } + + /// Use this method to send general files. + /// + /// Bots can currently send files of any type of up to 50 MB in size, this + /// limit may be changed in the future. + /// + /// [The official docs](https://core.telegram.org/bots/api#senddocument). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup or channel (in the format `@channelusername`). + /// - `document`: File to send. + /// + /// Pass a file_id as String to send a file that exists on the + /// Telegram servers (recommended), pass an HTTP URL as a String for + /// Telegram to get a file from the Internet, or upload a new one using + /// `multipart/form-data`. [More info on Sending Files Âģ]. + /// + /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files + /// + /// # Notes + /// Uses [a default parse mode] if specified in [`BotBuilder`]. + /// + /// [a default parse mode]: crate::BotBuilder::parse_mode + /// [`BotBuilder`]: crate::BotBuilder + pub fn send_document(&self, chat_id: C, document: InputFile) -> SendDocument + where + C: Into, + { + self.with_default_parse_mode_if_specified( + SendDocument::new(self.clone(), chat_id, document), + SendDocument::parse_mode, + ) + } + + /// Use this method to send video files, Telegram clients support mp4 videos + /// (other formats may be sent as Document). + /// + /// Bots can currently send video files of up to 50 MB in size, this + /// limit may be changed in the future. + /// + /// [The official docs](https://core.telegram.org/bots/api#sendvideo). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup or channel (in the format `@channelusername`). + /// - `video`: Video to sent. + /// + /// Pass [`InputFile::File`] to send a file that exists on + /// the Telegram servers (recommended), pass an [`InputFile::Url`] for + /// Telegram to get a .webp file from the Internet, or upload a new one + /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. + /// + /// [`InputFile::File`]: crate::types::InputFile::File + /// [`InputFile::Url`]: crate::types::InputFile::Url + /// [`InputFile::FileId`]: crate::types::InputFile::FileId + /// + /// # Notes + /// Uses [a default parse mode] if specified in [`BotBuilder`]. + /// + /// [a default parse mode]: crate::BotBuilder::parse_mode + /// [`BotBuilder`]: crate::BotBuilder + pub fn send_video(&self, chat_id: C, video: InputFile) -> SendVideo + where + C: Into, + { + self.with_default_parse_mode_if_specified( + SendVideo::new(self.clone(), chat_id, video), + SendVideo::parse_mode, + ) + } + + /// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video + /// without sound). + /// + /// Bots can currently send animation files of up to 50 MB in size, this + /// limit may be changed in the future. + /// + /// [The official docs](https://core.telegram.org/bots/api#sendanimation). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup or channel (in the format `@channelusername`). + /// - `animation`: Animation to send. + /// + /// # Notes + /// Uses [a default parse mode] if specified in [`BotBuilder`]. + /// + /// [a default parse mode]: crate::BotBuilder::parse_mode + /// [`BotBuilder`]: crate::BotBuilder + pub fn send_animation(&self, chat_id: C, animation: InputFile) -> SendAnimation + where + C: Into, + { + self.with_default_parse_mode_if_specified( + SendAnimation::new(self.clone(), chat_id, animation), + SendAnimation::parse_mode, + ) + } + + /// Use this method to send audio files, if you want Telegram clients to + /// display the file as a playable voice message. + /// + /// For this to work, your audio must be in an .ogg file encoded with OPUS + /// (other formats may be sent as [`Audio`] or [`Document`]). Bots can + /// currently send voice messages of up to 50 MB in size, this limit may + /// be changed in the future. + /// + /// [The official docs](https://core.telegram.org/bots/api#sendvoice). + /// + /// [`Audio`]: crate::types::Audio + /// [`Document`]: crate::types::Document + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup or channel (in the format `@channelusername`). + /// - `voice`: Audio file to send. + /// + /// Pass [`InputFile::File`] to send a file that exists on + /// the Telegram servers (recommended), pass an [`InputFile::Url`] for + /// Telegram to get a .webp file from the Internet, or upload a new one + /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. + /// + /// [`InputFile::File`]: crate::types::InputFile::File + /// [`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 + /// + /// # Notes + /// Uses [a default parse mode] if specified in [`BotBuilder`]. + /// + /// [a default parse mode]: crate::BotBuilder::parse_mode + /// [`BotBuilder`]: crate::BotBuilder + pub fn send_voice(&self, chat_id: C, voice: InputFile) -> SendVoice + where + C: Into, + { + self.with_default_parse_mode_if_specified( + SendVoice::new(self.clone(), chat_id, voice), + SendVoice::parse_mode, + ) + } + + /// 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. + /// + /// [The official docs](https://core.telegram.org/bots/api#sendvideonote). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup or channel (in the format `@channelusername`). + /// - `video_note`: Video note to send. + /// + /// Pass [`InputFile::File`] to send a file that exists on the Telegram + /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a + /// .webp file from the Internet, or upload a new one using + /// [`InputFile::FileId`]. [More info on Sending Files Âģ]. + /// + /// [v.4.0]: https://telegram.org/blog/video-messages-and-telescope + /// [`InputFile::File`]: crate::types::InputFile::File + /// [`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_video_note(&self, chat_id: C, video_note: InputFile) -> SendVideoNote + where + C: Into, + { + SendVideoNote::new(self.clone(), chat_id, video_note) + } + + /// Use this method to send a group of photos or videos as an album. + /// + /// [The official docs](https://core.telegram.org/bots/api#sendmediagroup). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// 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 + where + C: Into, + M: Into>, + { + SendMediaGroup::new(self.clone(), chat_id, media) + } + + /// Use this method to send point on the map. + /// + /// [The official docs](https://core.telegram.org/bots/api#sendlocation). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup or channel (in the format `@channelusername`). + /// - `latitude`: Latitude of the location. + /// - `longitude`: Latitude of the location. + pub fn send_location(&self, chat_id: C, latitude: f32, longitude: f32) -> SendLocation + where + C: Into, + { + SendLocation::new(self.clone(), chat_id, latitude, longitude) + } + + /// Use this method to edit live location messages. + /// + /// A location can be edited until its live_period expires or editing is + /// explicitly disabled by a call to stopMessageLiveLocation. On success, + /// the edited [`Message`] is returned. + /// + /// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation). + /// + /// [`Message`]: crate::types::Message + /// + /// # Params + /// - `latitude`: Latitude of new location. + /// - `longitude`: Longitude of new location. + pub fn edit_message_live_location( + &self, + chat_id: C, + message_id: i32, + latitude: f32, + longitude: f32, + ) -> EditMessageLiveLocation + where + C: Into, + { + EditMessageLiveLocation::new(self.clone(), chat_id, message_id, latitude, longitude) + } + + /// Use this method to edit live location messages sent via the bot. + /// + /// A location can be edited until its live_period expires or editing is + /// explicitly disabled by a call to stopMessageLiveLocation. On success, + /// [`True`] is returned. + /// + /// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation). + /// + /// [`True`]: crate::types::True + /// + /// # Params + /// - `latitude`: Latitude of new location. + /// - `longitude`: Longitude of new location. + pub fn edit_inline_message_live_location( + &self, + inline_message_id: I, + latitude: f32, + longitude: f32, + ) -> EditInlineMessageLiveLocation + where + I: Into, + { + EditInlineMessageLiveLocation::new(self.clone(), inline_message_id, latitude, longitude) + } + + /// Use this method to stop updating a live location message before + /// `live_period` expires. + /// + /// On success, the sent [`Message`] is returned. + /// + /// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation). + /// + /// [`Message`]: crate::types::Message + pub fn stop_message_live_location( + &self, + chat_id: C, + message_id: i32, + ) -> StopMessageLiveLocation + where + C: Into, + { + StopMessageLiveLocation::new(self.clone(), chat_id, message_id) + } + + /// Use this method to stop updating a live location message (sent via the + /// bot) before `live_period` expires. + /// + /// On success, [`True`] is returned. + /// + /// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation). + /// + /// [`True`]: crate::types::True + pub fn stop_inline_message_live_location( + &self, + inline_message_id: I, + ) -> StopInlineMessageLiveLocation + where + I: Into, + { + StopInlineMessageLiveLocation::new(self.clone(), inline_message_id) + } + + /// Use this method to send information about a venue. + /// + /// [The official docs](https://core.telegram.org/bots/api#sendvenue). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup or channel (in the format `@channelusername`). + /// - `latitude`: Latitude of the venue. + /// - `longitude`: Longitude of the venue. + /// - `title`: Name of the venue. + /// - `address`: Address of the venue. + pub fn send_venue( + &self, + chat_id: C, + latitude: f32, + longitude: f32, + title: T, + address: A, + ) -> SendVenue + where + C: Into, + T: Into, + A: Into, + { + SendVenue::new(self.clone(), chat_id, latitude, longitude, title, address) + } + + /// Use this method to send phone contacts. + /// + /// [The official docs](https://core.telegram.org/bots/api#sendcontact). + /// + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup or channel (in the format `@channelusername`). + /// - `phone_number`: Contact's phone number. + /// - `first_name`: Contact's first name. + pub fn send_contact(&self, chat_id: C, phone_number: P, first_name: F) -> SendContact + where + C: Into, + P: Into, + F: Into, + { + SendContact::new(self.clone(), chat_id, phone_number, first_name) + } + + /// Use this method to send a native poll. A native poll can't be sent to a + /// private chat. + /// + /// [The official docs](https://core.telegram.org/bots/api#sendpoll). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup or channel (in the format `@channelusername`). + /// - `question`: Poll question, 1-255 characters. + /// - `options`: List of answer options, 2-10 strings 1-100 characters + /// each. + /// + /// # Notes + /// Uses [a default parse mode] ([`SendPoll::explanation_parse_mode`]) if + /// specified in [`BotBuilder`]. + /// + /// [a default parse mode]: crate::BotBuilder::parse_mode + /// [`BotBuilder`]: crate::BotBuilder + /// [`SendPoll::explanation_parse_mode`]: + /// [`SendPoll::explanation_parse_mode`]: + /// crate::types::SendPoll::explanation_parse_mode + pub fn send_poll(&self, chat_id: C, question: Q, options: O) -> SendPoll + where + C: Into, + Q: Into, + O: Into>, + { + self.with_default_parse_mode_if_specified( + SendPoll::new(self.clone(), chat_id, question, options), + SendPoll::explanation_parse_mode, + ) + } + + /// Use this method when you need to tell the user that something is + /// happening on the bot's side. + /// + /// The status is set for 5 seconds or less (when a message arrives from + /// your bot, Telegram clients clear its typing status). + /// + /// ## Note + /// Example: The [ImageBot] needs some time to process a request and upload + /// the image. Instead of sending a text message along the lines of + /// “Retrieving image, please waitâ€Ļ”, the bot may use + /// [`Bot::send_chat_action`] with `action = upload_photo`. The user + /// will see a `sending photo` status for the bot. + /// + /// We only recommend using this method when a response from the bot will + /// take a **noticeable** amount of time to arrive. + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup or channel (in the format `@channelusername`). + /// + /// [ImageBot]: https://t.me/imagebot + /// [`Bot::send_chat_action`]: crate::Bot::send_chat_action + pub fn send_chat_action(&self, chat_id: C, action: SendChatActionKind) -> SendChatAction + where + C: Into, + { + SendChatAction::new(self.clone(), chat_id, action) + } + + /// Use this method to get a list of profile pictures for a user. + /// + /// [The official docs](https://core.telegram.org/bots/api#getuserprofilephotos). + /// + /// # Params + /// - `user_id`: Unique identifier of the target user. + pub fn get_user_profile_photos(&self, user_id: i32) -> GetUserProfilePhotos { + GetUserProfilePhotos::new(self.clone(), user_id) + } + + /// Use this method to get basic info about a file and prepare it for + /// downloading. + /// + /// For the moment, bots can download files of up to `20MB` in size. + /// + /// The file can then be downloaded via the link + /// `https://api.telegram.org/file/bot/`, where `` + /// is taken from the response. 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 [`GetFile`] again. + /// + /// **Note**: This function may not preserve the original file name and MIME + /// type. You should save the file's MIME type and name (if available) when + /// the [`File`] object is received. + /// + /// [The official docs](https://core.telegram.org/bots/api#getfile). + /// + /// # Params + /// - `file_id`: File identifier to get info about. + /// + /// [`File`]: crate::types::File + /// [`GetFile`]: self::GetFile + pub fn get_file(&self, file_id: F) -> GetFile + where + F: Into, + { + GetFile::new(self.clone(), file_id) + } + + /// Use this method to kick a user from a group, a supergroup or a channel. + /// + /// In the case of supergroups and channels, the user will not be able to + /// return to the group on their own using invite links, etc., unless + /// [unbanned] first. The bot must be an administrator in the chat for + /// this to work and must have the appropriate admin rights. + /// + /// [The official docs](https://core.telegram.org/bots/api#kickchatmember). + /// + /// # Params + /// - `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. + /// + /// [unbanned]: crate::Bot::unban_chat_member + pub fn kick_chat_member(&self, chat_id: C, user_id: i32) -> KickChatMember + where + C: Into, + { + KickChatMember::new(self.clone(), chat_id, user_id) + } + + /// 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, but will be able to join via link, etc. The bot must + /// be an administrator for this to work. + /// + /// [The official docs](https://core.telegram.org/bots/api#unbanchatmember). + /// + /// # Params + /// - `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 unban_chat_member(&self, chat_id: C, user_id: i32) -> UnbanChatMember + where + C: Into, + { + UnbanChatMember::new(self.clone(), chat_id, user_id) + } + + /// Use this method to restrict a user in a supergroup. + /// + /// The bot must be an administrator in the supergroup for this to work and + /// must have the appropriate admin rights. Pass `true` for all + /// permissions to lift restrictions from a user. + /// + /// [The official docs](https://core.telegram.org/bots/api#restrictchatmember). + /// + /// # Params + /// - `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. + /// - `permissions`: New user permissions. + pub fn restrict_chat_member( + &self, + chat_id: C, + user_id: i32, + permissions: ChatPermissions, + ) -> RestrictChatMember + where + C: Into, + { + RestrictChatMember::new(self.clone(), chat_id, user_id, permissions) + } + + /// Use this method to promote or demote a user in a supergroup or a + /// channel. + /// + /// The bot must be an administrator in the chat for this to work and must + /// have the appropriate admin rights. Pass False for all boolean + /// parameters to demote a user. + /// + /// [The official docs](https://core.telegram.org/bots/api#promotechatmember). + /// + /// # Params + /// - `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 promote_chat_member(&self, chat_id: C, user_id: i32) -> PromoteChatMember + where + C: Into, + { + PromoteChatMember::new(self.clone(), chat_id, user_id) + } + + /// Use this method to set default chat permissions for all members. + /// + /// The bot must be an administrator in the group or a supergroup for this + /// to work and must have the can_restrict_members admin rights. + /// + /// [The official docs](https://core.telegram.org/bots/api#setchatpermissions). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup or channel (in the format `@channelusername`). + /// - `permissions`: New default chat permissions. + pub fn set_chat_permissions( + &self, + chat_id: C, + permissions: ChatPermissions, + ) -> SetChatPermissions + where + C: Into, + { + SetChatPermissions::new(self.clone(), chat_id, permissions) + } + + /// Use this method to generate a new invite link for a chat; any previously + /// generated link is revoked. + /// + /// The bot must be an administrator in the chat for this to work and must + /// have the appropriate admin rights. + /// + /// # Note + /// Each administrator in a chat generates their own invite links. Bots + /// can't use invite links generated by other administrators. If you + /// want your bot to work with invite links, it will need to generate + /// its own link using [`Bot::export_chat_invite_link`] – after this the + /// link will become available to the bot via the [`Bot::get_chat`] + /// method. If your bot needs to generate a new invite link replacing + /// its previous one, use [`Bot::export_chat_invite_link`] again. + /// + /// [The official docs](https://core.telegram.org/bots/api#exportchatinvitelink). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup or channel (in the format `@channelusername`). + /// + /// [`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 + where + C: Into, + { + ExportChatInviteLink::new(self.clone(), chat_id) + } + + /// Use this method to set a new profile photo for the chat. + /// + /// Photos can't be changed for private chats. The bot must be an + /// administrator in the chat for this to work and must have the + /// appropriate admin rights. + /// + /// [The official docs](https://core.telegram.org/bots/api#setchatphoto). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup or channel (in the format `@channelusername`). + /// - `photo`: New chat photo, uploaded using `multipart/form-data`. + pub fn set_chat_photo(&self, chat_id: C, photo: InputFile) -> SetChatPhoto + where + C: Into, + { + SetChatPhoto::new(self.clone(), chat_id, photo) + } + + /// 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 must have the appropriate admin rights. + /// + /// [The official docs](https://core.telegram.org/bots/api#deletechatphoto). + /// + /// # 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 + where + C: Into, + { + DeleteChatPhoto::new(self.clone(), chat_id) + } + + /// Use this method to change the title of a chat. + /// + /// Titles can't be changed for private chats. The bot must be an + /// administrator in the chat for this to work and must have the + /// appropriate admin rights. + /// + /// [The official docs](https://core.telegram.org/bots/api#setchattitle). + /// + /// # Params + /// - `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 + where + C: Into, + T: Into, + { + SetChatTitle::new(self.clone(), chat_id, title) + } + + /// Use this method to change the description of a group, a supergroup or a + /// channel. + /// + /// The bot must be an administrator in the chat for this to work and must + /// have the appropriate admin rights. + /// + /// [The official docs](https://core.telegram.org/bots/api#setchatdescription). + /// + /// # 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 + where + C: Into, + { + SetChatDescription::new(self.clone(), chat_id) + } + + /// Use this method to pin a message in a group, a supergroup, or a channel. + /// + /// The bot must be an administrator in the chat for this to work and must + /// have the `can_pin_messages` admin right in the supergroup or + /// `can_edit_messages` admin right in the channel. + /// + /// [The official docs](https://core.telegram.org/bots/api#pinchatmessage). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup or channel (in the format `@channelusername`). + /// - `message_id`: Identifier of a message to pin. + pub fn pin_chat_message(&self, chat_id: C, message_id: i32) -> PinChatMessage + where + C: Into, + { + PinChatMessage::new(self.clone(), chat_id, message_id) + } + + /// Use this method to unpin a message in a group, a supergroup, or a + /// channel. + /// + /// The bot must be an administrator in the chat for this to work and must + /// have the `can_pin_messages` admin right in the supergroup or + /// `can_edit_messages` admin right in the channel. + /// + /// [The official docs](https://core.telegram.org/bots/api#unpinchatmessage). + /// + /// # 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 + where + C: Into, + { + UnpinChatMessage::new(self.clone(), chat_id) + } + + /// Use this method for your bot to leave a group, supergroup or channel. + /// + /// [The official docs](https://core.telegram.org/bots/api#leavechat). + /// + /// # 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 + where + C: Into, + { + LeaveChat::new(self.clone(), chat_id) + } + + /// 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 or channel, etc.). + /// + /// [The official docs](https://core.telegram.org/bots/api#getchat). + /// + /// # 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 + where + C: Into, + { + GetChat::new(self.clone(), chat_id) + } + + /// Use this method to get a list of administrators in a chat. + /// + /// If the chat is a group or a supergroup and no administrators were + /// appointed, only the creator will be returned. + /// + /// [The official docs](https://core.telegram.org/bots/api#getchatadministrators). + /// + /// # 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_administrators(&self, chat_id: C) -> GetChatAdministrators + where + C: Into, + { + GetChatAdministrators::new(self.clone(), chat_id) + } + + /// Use this method to get the number of members in a chat. + /// + /// [The official docs](https://core.telegram.org/bots/api#getchatmemberscount). + /// + /// # 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 + where + C: Into, + { + GetChatMembersCount::new(self.clone(), chat_id) + } + + /// Use this method to get information about a member of a chat. + /// + /// [The official docs](https://core.telegram.org/bots/api#getchatmember). + /// + /// # Params + /// - `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 + where + C: Into, + { + GetChatMember::new(self.clone(), chat_id, user_id) + } + + /// Use this method to set a new group sticker set for a supergroup. + /// + /// The bot must be an administrator in the chat for this to work and must + /// have the appropriate admin rights. Use the field can_set_sticker_set + /// optionally returned in getChat requests to check if the bot can use + /// this method. + /// + /// [The official docs](https://core.telegram.org/bots/api#setchatstickerset). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup (in the format `@supergroupusername`). + /// - `sticker_set_name`: Name of the sticker set to be set as the group + /// sticker set. + pub fn set_chat_sticker_set(&self, chat_id: C, sticker_set_name: S) -> SetChatStickerSet + where + C: Into, + S: Into, + { + SetChatStickerSet::new(self.clone(), chat_id, sticker_set_name) + } + + /// Use this method to delete a group sticker set from a supergroup. + /// + /// The bot must be an administrator in the chat for this to work and must + /// have the appropriate admin rights. Use the field + /// `can_set_sticker_set` optionally returned in [`Bot::get_chat`] + /// requests to check if the bot can use this method. + /// + /// [The official docs](https://core.telegram.org/bots/api#deletechatstickerset). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target supergroup (in the format `@supergroupusername`). + /// + /// [`Bot::get_chat`]: crate::Bot::get_chat + pub fn delete_chat_sticker_set(&self, chat_id: C) -> DeleteChatStickerSet + where + C: Into, + { + DeleteChatStickerSet::new(self.clone(), chat_id) + } + + /// Use this method to send answers to callback queries sent from [inline + /// keyboards]. + /// + /// The answer will be displayed to the user as a notification at + /// the top of the chat screen or as an alert. + /// + /// [The official docs](https://core.telegram.org/bots/api#answercallbackquery). + /// + /// # Params + /// - `callback_query_id`: Unique identifier for the query to be answered. + /// + /// [inline keyboards]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub fn answer_callback_query(&self, callback_query_id: C) -> AnswerCallbackQuery + where + C: Into, + { + AnswerCallbackQuery::new(self.clone(), callback_query_id) + } + + /// Use this method to edit text and game messages. + /// + /// On success, the edited [`Message`] is returned. + /// + /// [The official docs](https://core.telegram.org/bots/api#editmessagetext). + /// + /// [`Message`]: crate::types::Message + /// + /// # Params + /// + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target channel (in the format `@channelusername`). + /// - `message_id`: Identifier of the message to edit. + /// - `text`: New text of the message. + /// + /// # Notes + /// + /// Uses [a default parse mode] if specified in [`BotBuilder`]. + /// + /// [a default parse mode]: crate::BotBuilder::parse_mode + /// [`BotBuilder`]: crate::BotBuilder + pub fn edit_message_text(&self, chat_id: C, message_id: i32, text: T) -> EditMessageText + where + C: Into, + T: Into, + { + match self.parse_mode { + None => EditMessageText::new(self.clone(), chat_id, message_id, text), + Some(parse_mode) => { + EditMessageText::new(self.clone(), chat_id, message_id, text).parse_mode(parse_mode) + } + } + } + + /// Use this method to edit text and game messages sent via the bot. + /// + /// On success, [`True`] is returned. + /// + /// [The official docs](https://core.telegram.org/bots/api#editmessagetext). + /// + /// [`True`]: crate::types::True + /// + /// # Params + /// + /// - `inline_message_id`: Identifier of the inline message. + /// - `text`: New text of the message. + /// + /// # Notes + /// + /// Uses [a default parse mode] if specified in [`BotBuilder`]. + /// + /// [a default parse mode]: crate::BotBuilder::parse_mode + /// [`BotBuilder`]: crate::BotBuilder + pub fn edit_inline_message_text( + &self, + inline_message_id: I, + text: T, + ) -> EditInlineMessageText + where + I: Into, + T: Into, + { + match self.parse_mode { + None => EditInlineMessageText::new(self.clone(), inline_message_id, text), + Some(parse_mode) => EditInlineMessageText::new(self.clone(), inline_message_id, text) + .parse_mode(parse_mode), + } + } + + /// Use this method to edit captions of messages sent via the bot. + /// + /// On success, [`True`] is returned. + /// + /// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). + /// + /// [`True`]: crate::types::True + /// + /// # Notes + /// + /// Uses [a default parse mode] if specified in [`BotBuilder`]. + /// + /// [a default parse mode]: crate::BotBuilder::parse_mode + /// [`BotBuilder`]: crate::BotBuilder + pub fn edit_message_caption(&self, chat_id: C, message_id: i32) -> EditMessageCaption + where + C: Into, + { + match self.parse_mode { + None => EditMessageCaption::new(self.clone(), chat_id, message_id), + Some(parse_mode) => { + EditMessageCaption::new(self.clone(), chat_id, message_id).parse_mode(parse_mode) + } + } + } + + /// Use this method to edit captions of messages sent via the bot. + /// + /// On success, [`True`] is returned. + /// + /// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). + /// + /// [`True`]: crate::types::True + /// + /// # Notes + /// Uses [a default parse mode] if specified in [`BotBuilder`]. + /// + /// [a default parse mode]: crate::BotBuilder::parse_mode + /// [`BotBuilder`]: crate::BotBuilder + pub fn edit_inline_message_caption(&self, inline_message_id: I) -> EditInlineMessageCaption + where + I: Into, + { + match self.parse_mode { + None => EditInlineMessageCaption::new(self.clone(), inline_message_id), + Some(parse_mode) => EditInlineMessageCaption::new(self.clone(), inline_message_id) + .parse_mode(parse_mode), + } + } + + /// Use this method to edit animation, audio, document, photo, or video + /// messages. + /// + /// If a message is a part of a message album, then it can be edited only to + /// a photo or a video. Otherwise, message type can be changed + /// arbitrarily. On success, the edited [`Message`] is returned. + /// + /// [The official docs](https://core.telegram.org/bots/api#editmessagemedia). + /// + /// [`Message`]: crate::types::Message + pub fn edit_message_media( + &self, + chat_id: C, + message_id: i32, + media: InputMedia, + ) -> EditMessageMedia + where + C: Into, + { + EditMessageMedia::new(self.clone(), chat_id, message_id, media) + } + + /// Use this method to edit animation, audio, document, photo, or video + /// messages sent via the bot. + /// + /// If a message is a part of a message album, then it can be edited only to + /// a photo or a video. Otherwise, message type can be changed + /// arbitrarily. When this method is used, new file can't be uploaded. + /// Use previously uploaded file via its `file_id` or specify a URL. On + /// success, [`True`] is returned. + /// + /// [The official docs](https://core.telegram.org/bots/api#editmessagemedia). + /// + /// [`True`]: crate::types::True + pub fn edit_inline_message_media( + &self, + inline_message_id: I, + media: InputMedia, + ) -> EditInlineMessageMedia + where + I: Into, + { + EditInlineMessageMedia::new(self.clone(), inline_message_id, media) + } + + /// Use this method to edit only the reply markup of messages. + /// + /// On success, the edited [`Message`] is returned. + /// + /// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup). + /// + /// [`Message`]: crate::types::Message + pub fn edit_message_reply_markup( + &self, + chat_id: C, + message_id: i32, + ) -> EditMessageReplyMarkup + where + C: Into, + { + EditMessageReplyMarkup::new(self.clone(), chat_id, message_id) + } + + /// Use this method to edit only the reply markup of messages sent via the + /// bot. + /// + /// On success, [`True`] is returned. + /// + /// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup). + /// + /// [`Message`]: crate::types::Message + /// [`True`]: crate::types::True + pub fn edit_inline_message_reply_markup( + &self, + inline_message_id: I, + ) -> EditInlineMessageReplyMarkup + where + I: Into, + { + EditInlineMessageReplyMarkup::new(self.clone(), inline_message_id) + } + + /// Use this method to stop a poll which was sent by the bot. + /// + /// [The official docs](https://core.telegram.org/bots/api#stoppoll). + /// + /// + /// # Params + /// - `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 + where + C: Into, + { + StopPoll::new(self.clone(), chat_id, message_id) + } + + /// Use this method to delete a message, including service messages. + /// + /// The limitations are: + /// - A message can only be deleted if it was sent less than 48 hours ago. + /// - Bots can delete outgoing messages in private chats, groups, and + /// supergroups. + /// - Bots can delete incoming messages in private chats. + /// - Bots granted can_post_messages permissions can delete outgoing + /// messages in channels. + /// - If the bot is an administrator of a group, it can delete any message + /// there. + /// - If the bot has can_delete_messages permission in a supergroup or a + /// channel, it can delete any message there. + /// + /// [The official docs](https://core.telegram.org/bots/api#deletemessage). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target channel (in the format `@channelusername`). + /// - `message_id`: Identifier of the message to delete. + pub fn delete_message(&self, chat_id: C, message_id: i32) -> DeleteMessage + where + C: Into, + { + DeleteMessage::new(self.clone(), chat_id, message_id) + } + + /// Use this method to send static .WEBP or [animated] .TGS stickers. + /// + /// [The official docs](https://core.telegram.org/bots/api#sendsticker). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target channel (in the format `@channelusername`). + /// - `sticker`: Sticker to send. + /// + /// Pass [`InputFile::File`] to send a file that exists on the Telegram + /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a + /// .webp file from the Internet, or upload a new one using + /// [`InputFile::FileId`]. [More info on Sending Files Âģ]. + /// + /// [animated]: https://telegram.org/blog/animated-stickers + /// [`InputFile::File`]: crate::types::InputFile::File + /// [`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 + where + C: Into, + { + SendSticker::new(self.clone(), chat_id, sticker) + } + + /// Use this method to get a sticker set. + /// + /// [The official docs](https://core.telegram.org/bots/api#getstickerset). + /// + /// # Params + /// - `name`: Name of the sticker set. + pub fn get_sticker_set(&self, name: N) -> GetStickerSet + where + N: Into, + { + GetStickerSet::new(self.clone(), name) + } + + /// 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 be used multiple times). + /// + /// [The official docs](https://core.telegram.org/bots/api#uploadstickerfile). + /// + /// # Params + /// - `user_id`: User identifier of sticker file owner. + /// - `png_sticker`: **Png** image with the sticker, must be up to 512 + /// kilobytes in size, dimensions must not exceed 512px, and either + /// width or height must be exactly 512px. [More info on Sending Files + /// Âģ]. + /// + /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files + /// [`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, user_id: i32, png_sticker: InputFile) -> UploadStickerFile { + UploadStickerFile::new(self.clone(), user_id, png_sticker) + } + + /// 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). + /// + /// # Params + /// - `user_id`: User identifier of created sticker set owner. + /// - `name`: Short name of sticker set, to be used in `t.me/addstickers/` + /// URLs (e.g., animals). Can contain only english letters, digits and + /// underscores. + /// + /// Must begin with a letter, can't contain consecutive underscores and must + /// end in `_by_`. `` is case insensitive. 1-64 + /// characters. + /// - `title`: Sticker set title, 1-64 characters. + pub fn create_new_sticker_set( + &self, + user_id: i32, + name: N, + title: T, + sticker_type: StickerType, + emojis: E, + ) -> CreateNewStickerSet + where + N: Into, + T: Into, + E: Into, + { + CreateNewStickerSet::new(self.clone(), user_id, name, title, sticker_type, emojis) + } + + /// 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). + /// + /// # Params + /// - `user_id`: User identifier of sticker set owner. + /// - `name`: Sticker set name. + /// - `emojis`: One or more emoji corresponding to the sticker. + pub fn add_sticker_to_set( + &self, + user_id: i32, + name: N, + sticker_type: StickerType, + emojis: E, + ) -> AddStickerToSet + where + N: Into, + E: Into, + { + AddStickerToSet::new(self.clone(), user_id, name, sticker_type, emojis) + } + + /// 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). + /// + /// # Params + /// - `sticker`: File identifier of the sticker. + /// - `position`: New sticker position in the set, zero-based. + pub fn set_sticker_position_in_set( + &self, + sticker: S, + position: i32, + ) -> SetStickerPositionInSet + where + S: Into, + { + SetStickerPositionInSet::new(self.clone(), sticker, position) + } + + /// Use this method to delete a sticker from a set created by the bot. + /// + /// [The official docs](https://core.telegram.org/bots/api#deletestickerfromset). + /// + /// # Params + /// - `sticker`: File identifier of the sticker. + pub fn delete_sticker_from_set(&self, sticker: S) -> DeleteStickerFromSet + where + S: Into, + { + DeleteStickerFromSet::new(self.clone(), sticker) + } + + /// Use this method to send answers to an inline query. + /// + /// No more than **50** results per query are allowed. + /// + /// [The official docs](https://core.telegram.org/bots/api#answerinlinequery). + /// + /// # Params + /// - `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, inline_query_id: I, results: R) -> AnswerInlineQuery + where + I: Into, + R: Into>, + { + AnswerInlineQuery::new(self.clone(), inline_query_id, results) + } + + /// Use this method to send invoices. + /// + /// [The official docs](https://core.telegram.org/bots/api#sendinvoice). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target private chat. + /// - `title`: Product name, 1-32 characters. + /// - `description`: Product description, 1-255 characters. + /// - `payload`: Bot-defined invoice payload, 1-128 bytes. This will not + /// be displayed to the user, use for your internal processes. + /// - `provider_token`: Payments provider token, obtained via + /// [@Botfather]. + /// - `start_parameter`: Unique deep-linking parameter that can be used to + /// generate this invoice when used as a start parameter. + /// - `currency`: Three-letter ISO 4217 currency code, see [more on + /// currencies]. + /// - `prices`: Price breakdown, a list of components (e.g. product price, + /// tax, discount, delivery cost, delivery tax, bonus, etc.). + /// + /// [more on currencies]: https://core.telegram.org/bots/payments#supported-currencies + /// [@Botfather]: https://t.me/botfather + #[allow(clippy::too_many_arguments)] + pub fn send_invoice( + &self, + chat_id: i32, + title: T, + description: D, + payload: Pl, + provider_token: Pt, + start_parameter: S, + currency: C, + prices: Pr, + ) -> SendInvoice + where + T: Into, + D: Into, + Pl: Into, + Pt: Into, + S: Into, + C: Into, + Pr: Into>, + { + SendInvoice::new( + self.clone(), + chat_id, + title, + description, + payload, + provider_token, + start_parameter, + currency, + prices, + ) + } + + /// 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 `pre_checkout_query`. Use this method to respond to such + /// pre-checkout queries. Note: The Bot API must receive an answer + /// within 10 seconds after the pre-checkout query was sent. + /// + /// [The official docs](https://core.telegram.org/bots/api#answerprecheckoutquery). + /// + /// # Params + /// - `shipping_query_id`: Unique identifier for the query to be answered. + /// - `ok`: Specify `true` if delivery to the specified address is + /// possible and `false` if there are any problems (for example, if + /// delivery to the specified address is not possible). + /// + /// [`Update`]: crate::types::Update + pub fn answer_shipping_query(&self, shipping_query_id: S, ok: bool) -> AnswerShippingQuery + where + S: Into, + { + AnswerShippingQuery::new(self.clone(), shipping_query_id, ok) + } + + /// 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 `pre_checkout_query`. Use this method to respond to such + /// pre-checkout queries. Note: The Bot API must receive an answer + /// within 10 seconds after the pre-checkout query was sent. + /// + /// [The official docs](https://core.telegram.org/bots/api#answerprecheckoutquery). + /// + /// # Params + /// - `pre_checkout_query_id`: Unique identifier for the query to be + /// answered. + /// - `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, + pre_checkout_query_id: P, + ok: bool, + ) -> AnswerPreCheckoutQuery + where + P: Into, + { + AnswerPreCheckoutQuery::new(self.clone(), pre_checkout_query_id, ok) + } + + /// Use this method to send a game. + /// + /// [The official docs](https://core.telegram.org/bots/api#sendgame). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat. + /// - `game_short_name`: Short name of the game, serves as the unique + /// 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 + where + G: Into, + { + SendGame::new(self.clone(), chat_id, game_short_name) + } + + /// Use this method to set the score of the specified user in a game. + /// + /// On success, if the message was sent by the bot, returns the edited + /// [`Message`], otherwise returns [`True`]. Returns an error, if the new + /// score is not greater than the user's current score in the chat and + /// force is `false`. + /// + /// [The official docs](https://core.telegram.org/bots/api#setgamescore). + /// + /// # Params + /// - `target`: Target message, either chat id and message id or inline + /// message id. + /// - `user_id`: User identifier. + /// - `score`: New score, must be non-negative. + /// + /// [`Message`]: crate::types::Message + /// [`True`]: crate::types::True + pub fn set_game_score(&self, target: T, user_id: i32, score: i32) -> SetGameScore + where + T: Into, + { + SetGameScore::new(self.clone(), target, user_id, score) + } + + /// Use this method to get data for high score tables. + /// + /// Will return the score of the specified user and several of his neighbors + /// in a game. + /// + /// # Note + /// This method will currently return scores for the target user, plus two + /// of his closest neighbors on each side. Will also return the top + /// three users if the user and his neighbors are not among them. Please + /// note that this behavior is subject to change. + /// + /// [The official docs](https://core.telegram.org/bots/api#getgamehighscores). + /// + /// # Params + /// - `target`: Target message, either chat id and message id or inline + /// message id. + /// - `user_id`: Target user id. + pub fn get_game_high_scores(&self, target: T, user_id: i32) -> GetGameHighScores + where + T: Into, + { + GetGameHighScores::new(self.clone(), target, user_id) + } + + /// 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). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target channel (in the format `@channelusername`). + /// - `user_id`: Unique identifier of the target user. + /// - `custom_title`: New custom title for the administrator; 0-16 + /// characters, emoji are not allowed. + pub fn set_chat_administrator_custom_title( + &self, + chat_id: C, + user_id: i32, + custom_title: CT, + ) -> SetChatAdministratorCustomTitle + where + C: Into, + CT: Into, + { + SetChatAdministratorCustomTitle::new(self.clone(), chat_id, user_id, custom_title) + } + + /// Use this method to send an animated emoji that will display a random + /// value. + /// + /// [The official docs](https://core.telegram.org/bots/api#senddice). + /// + /// # Params + /// - `chat_id`: Unique identifier for the target chat or username of the + /// target channel (in the format `@channelusername`). + pub fn send_dice(&self, chat_id: C) -> SendDice + where + C: Into, + { + SendDice::new(self.clone(), chat_id) + } + + /// Use this method to get the current list of the bot's commands. + /// + /// [The official docs](https://core.telegram.org/bots/api#getmycommands). + pub fn get_my_commands(&self) -> GetMyCommands { + GetMyCommands::new(self.clone()) + } + + /// Use this method to change the list of the bot's commands. + /// + /// [The official docs](https://core.telegram.org/bots/api#setmycommands). + /// + /// # Params + /// - `commands`: A JSON-serialized list of bot commands to be set as the + /// list of the bot's commands. At most 100 commands can be specified. + pub fn set_my_commands(&self, commands: C) -> SetMyCommands + where + C: Into>, + { + SetMyCommands::new(self.clone(), commands) + } + + /// Use this method to set the thumbnail of a sticker set. Animated + /// thumbnails can be set for animated sticker sets only. + /// + /// [The official docs](https://core.telegram.org/bots/api#setstickersetthumb). + /// + /// # Params + /// - `name`: Sticker set name. + /// - `user_id`: User identifier of the sticker set owner. + pub fn set_sticker_set_thumb(&self, name: S, user_id: i32) -> SetStickerSetThumb + where + S: Into, + { + SetStickerSetThumb::new(self.clone(), name, user_id) + } + + fn with_default_parse_mode_if_specified( + &self, + builder: Builder, + f: fn(Builder, ParseMode) -> Builder, + ) -> Builder { + match self.parse_mode { + None => builder, + Some(parse_mode) => f(builder, parse_mode), + } + } +} diff --git a/src/bot/download.rs b/src/bot/download.rs new file mode 100644 index 00000000..7035cfe7 --- /dev/null +++ b/src/bot/download.rs @@ -0,0 +1,65 @@ +use tokio::io::AsyncWrite; + +#[cfg(feature = "unstable-stream")] +use ::{bytes::Bytes, tokio::stream::Stream}; + +#[cfg(feature = "unstable-stream")] +use crate::net::download_file_stream; +use crate::{bot::Bot, net::download_file, DownloadError}; + +impl Bot { + /// Download a file from Telegram into `destination`. + /// + /// `path` can be obtained from [`Bot::get_file`]. + /// + /// To download as a stream of chunks, see [`Bot::download_file_stream`]. + /// + /// ## Examples + /// + /// ```no_run + /// use teloxide::types::File as TgFile; + /// use tokio::fs::File; + /// # use teloxide::RequestError; + /// use teloxide::{requests::Request, Bot}; + /// + /// # async fn run() -> Result<(), Box> { + /// let bot = Bot::new("TOKEN"); + /// let mut file = File::create("/home/waffle/Pictures/test.png").await?; + /// + /// let TgFile { file_path, .. } = bot.get_file("*file_id*").send().await?; + /// bot.download_file(&file_path, &mut file).await?; + /// # Ok(()) } + /// ``` + /// + /// [`Bot::get_file`]: crate::Bot::get_file + /// [`Bot::download_file_stream`]: crate::Bot::download_file_stream + pub async fn download_file( + &self, + path: &str, + destination: &mut D, + ) -> Result<(), DownloadError> + where + D: AsyncWrite + Unpin, + { + download_file(&self.client, &self.token, path, destination).await + } + + /// Download a file from Telegram. + /// + /// `path` can be obtained from the [`Bot::get_file`]. + /// + /// To download into [`AsyncWrite`] (e.g. [`tokio::fs::File`]), see + /// [`Bot::download_file`]. + /// + /// [`Bot::get_file`]: crate::bot::Bot::get_file + /// [`AsyncWrite`]: tokio::io::AsyncWrite + /// [`tokio::fs::File`]: tokio::fs::File + /// [`Bot::download_file`]: crate::Bot::download_file + #[cfg(feature = "unstable-stream")] + pub async fn download_file_stream( + &self, + path: &str, + ) -> Result>, reqwest::Error> { + download_file_stream(&self.client, &self.token, path).await + } +} diff --git a/src/bot/mod.rs b/src/bot/mod.rs new file mode 100644 index 00000000..617ed664 --- /dev/null +++ b/src/bot/mod.rs @@ -0,0 +1,240 @@ +use crate::types::ParseMode; +use reqwest::{ + header::{HeaderMap, CONNECTION}, + Client, ClientBuilder, +}; +use std::{sync::Arc, time::Duration}; + +mod api; +mod download; + +pub(crate) const TELOXIDE_TOKEN: &str = "TELOXIDE_TOKEN"; +pub(crate) const TELOXIDE_PROXY: &str = "TELOXIDE_PROXY"; + +/// A requests sender. +/// +/// No need to put it into [`Arc`], because it's already in. +/// +/// [`Arc`]: std::sync::Arc +#[derive(Debug, Clone)] +pub struct Bot { + token: Arc, + client: Client, + parse_mode: Option, +} + +impl Bot { + /// Creates a new `Bot` with the `TELOXIDE_TOKEN` & `TELOXIDE_PROXY` + /// environmental variables (a bot's token & a proxy) and the default + /// [`reqwest::Client`]. + /// + /// This function passes the value of `TELOXIDE_PROXY` into + /// [`reqwest::Proxy::all`], if it exists, otherwise returns the default + /// client. + /// + /// # Panics + /// - If cannot get the `TELOXIDE_TOKEN` environmental variable. + /// - If it cannot create [`reqwest::Client`]. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + /// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all + #[must_use] + pub fn from_env() -> Self { + BotBuilder::new().build() + } + + /// 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. + /// + /// # Caution + /// Your custom client might not be configured correctly to be able to work + /// in long time durations, see [issue 223]. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + /// [issue 223]: https://github.com/teloxide/teloxide/issues/223 + #[deprecated] + #[allow(deprecated)] + pub fn from_env_with_client(client: Client) -> Self { + Self::with_client(&get_env(TELOXIDE_TOKEN), client) + } + + /// Creates a new `Bot` with the specified token and the default + /// [`reqwest::Client`]. + /// + /// # Panics + /// If it cannot create [`reqwest::Client`]. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html + #[deprecated] + #[allow(deprecated)] + pub fn new(token: S) -> Self + where + S: Into, + { + Self::with_client(token, build_sound_bot()) + } + + /// Creates a new `Bot` with the specified token and your + /// [`reqwest::Client`]. + /// + /// # Caution + /// Your custom client might not be configured correctly to be able to work + /// in long time durations, see [issue 223]. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html + /// [issue 223]: https://github.com/teloxide/teloxide/issues/223 + #[deprecated] + #[allow(deprecated)] + pub fn with_client(token: S, client: Client) -> Self + where + S: Into, + { + Self { + token: Into::>::into(Into::::into(token)), + client, + parse_mode: None, + } + } +} + +/// Returns a builder with safe settings. +/// +/// By "safe settings" I mean that a client will be able to work in long time +/// durations, see the [issue 223]. +/// +/// [issue 223]: https://github.com/teloxide/teloxide/issues/223 +pub(crate) fn sound_bot() -> ClientBuilder { + let mut headers = HeaderMap::new(); + headers.insert(CONNECTION, "keep-alive".parse().unwrap()); + + let connect_timeout = Duration::from_secs(5); + let timeout = 10; + + ClientBuilder::new() + .connect_timeout(connect_timeout) + .timeout(Duration::from_secs(connect_timeout.as_secs() + timeout + 2)) + .tcp_nodelay_(true) + .default_headers(headers) +} + +pub(crate) fn build_sound_bot() -> Client { + sound_bot().build().expect("creating reqwest::Client") +} + +fn get_env(env: &'static str) -> String { + std::env::var(env).unwrap_or_else(|_| panic!("Cannot get the {} env variable", env)) +} + +impl Bot { + pub fn token(&self) -> &str { + &self.token + } + + pub fn client(&self) -> &Client { + &self.client + } +} + +/// A builder of [`Bot`], supporting some extra settings. +/// +/// [`Bot`] crate::Bot +#[derive(Debug, Default)] +pub struct BotBuilder { + token: Option, + client: Option, + parse_mode: Option, +} + +impl BotBuilder { + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Specifies a custom HTTPS client. Otherwise, the default will be used. + /// + /// # Caution + /// - Your custom client might not be configured correctly to be able to + /// work + /// in long time durations, see [issue 223]. + /// + /// - If this method is used, the `TELOXIDE_PROXY` environmental variable + /// won't be extracted in [`BotBuilder::build`]. + /// + /// [issue 223]: https://github.com/teloxide/teloxide/issues/223 + /// [`BotBuilder::build`]: crate::BotBuilder::build + #[must_use] + pub fn client(mut self, client: Client) -> Self { + self.client = Some(client); + self + } + + /// Specified a custom token. + /// + /// Otherwise, a token will be extracted from the `TELOXIDE_TOKEN` + /// environmental variable. + #[must_use] + pub fn token(mut self, token: S) -> Self + where + S: Into, + { + self.token = Some(token.into()); + self + } + + /// Specifies [`ParseMode`], which will be used during all calls to: + /// + /// - [`send_message`] + /// - [`send_photo`] + /// - [`send_video`] + /// - [`send_audio`] + /// - [`send_document`] + /// - [`send_animation`] + /// - [`send_voice`] + /// - [`send_poll`] + /// - [`edit_message_text`] + /// - [`edit_message_caption`] + /// + /// [`send_message`]: crate::Bot::send_message + /// [`send_photo`]: crate::Bot::send_photo + /// [`send_video`]: crate::Bot::send_video + /// [`send_audio`]: crate::Bot::send_audio + /// [`send_document`]: crate::Bot::send_document + /// [`send_animation`]: crate::Bot::send_animation + /// [`send_voice`]: crate::Bot::send_voice + /// [`send_poll`]: crate::Bot::send_poll + /// [`edit_message_text`]: crate::Bot::edit_message_text + /// [`edit_message_caption`]: crate::Bot::edit_message_caption + #[must_use] + pub fn parse_mode(mut self, parse_mode: ParseMode) -> Self { + self.parse_mode = Some(parse_mode); + self + } + + /// Builds [`Bot`]. + /// + /// This method will attempt to build a new client with a proxy, specified + /// in the `TELOXIDE_PROXY` (passed into [`reqwest::Proxy::all`]) + /// environmental variable, if a client haven't been specified. If + /// `TELOXIDE_PROXY` is unspecified, it'll use no proxy. + /// + /// # Panics + /// - If cannot get the `TELOXIDE_TOKEN` environmental variable. + /// - If it cannot create [`reqwest::Client`]. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + /// + /// [`Bot`]: crate::Bot + /// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all + #[must_use] + pub fn build(self) -> Bot { + Bot { + client: self.client.unwrap_or_else(crate::client_from_env), + token: self.token.unwrap_or_else(|| get_env(TELOXIDE_TOKEN)).into(), + parse_mode: self.parse_mode, + } + } +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 00000000..466cc501 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,515 @@ +use derive_more::From; +use reqwest::StatusCode; +use serde::Deserialize; +use thiserror::Error; + +/// An error caused by downloading a file. +#[derive(Debug, Error, From)] +pub enum DownloadError { + #[error("A network error: {0}")] + NetworkError(#[source] reqwest::Error), + + #[error("An I/O error: {0}")] + Io(#[source] std::io::Error), +} + +/// An error caused by sending a request to Telegram. +#[derive(Debug, Error)] +pub enum RequestError { + #[error("A Telegram's error #{status_code}: {kind:?}")] + ApiError { status_code: StatusCode, kind: ApiErrorKind }, + + /// The group has been migrated to a supergroup with the specified + /// identifier. + #[error("The group has been migrated to a supergroup with ID #{0}")] + MigrateToChatId(i64), + + /// In case of exceeding flood control, the number of seconds left to wait + /// before the request can be repeated. + #[error("Retry after {0} seconds")] + RetryAfter(i32), + + #[error("A network error: {0}")] + NetworkError(#[source] reqwest::Error), + + #[error("An error while parsing JSON: {0}")] + InvalidJson(#[source] serde_json::Error), +} + +/// A kind of an API error. +/// +/// If you receive [`ApiErrorKind::Unknown`], please [open an issue] with +/// the description of the error. +/// +/// [`ApiErrorKind::Unknown`]: crate::ApiErrorKind::Unknown +/// [open an issue]: https://github.com/teloxide/teloxide/issues/new +#[derive(Debug, Deserialize, PartialEq, Hash, Eq, Clone)] +#[serde(untagged)] +pub enum ApiErrorKind { + Known(KnownApiErrorKind), + Unknown(String), +} + +/// A kind of a known API error. +#[derive(Debug, Deserialize, PartialEq, Copy, Hash, Eq, Clone)] +pub enum KnownApiErrorKind { + /// Occurs when the bot tries to send message to user who blocked the bot. + #[serde(rename = "Forbidden: bot was blocked by the user")] + BotBlocked, + + /// Occurs when bot tries to modify a message without modification content. + /// + /// May happen in methods: + /// 1. [`EditMessageText`] + /// + /// [`EditMessageText`]: crate::requests::EditMessageText + #[serde(rename = "Bad Request: message is not modified: specified new message content and \ + reply markup are exactly the same as a current content and reply markup \ + of the message")] + MessageNotModified, + + /// Occurs when bot tries to forward or delete a message which was deleted. + /// + /// May happen in methods: + /// 1. [`ForwardMessage`] + /// 2. [`DeleteMessage`] + /// + /// [`ForwardMessage`]: crate::requests::ForwardMessage + /// [`DeleteMessage`]: crate::requests::DeleteMessage + #[serde(rename = "Bad Request: MESSAGE_ID_INVALID")] + MessageIdInvalid, + + /// Occurs when bot tries to forward a message which does not exists. + /// + /// May happen in methods: + /// 1. [`ForwardMessage`] + /// + /// [`ForwardMessage`]: crate::requests::ForwardMessage + #[serde(rename = "Bad Request: message to forward not found")] + MessageToForwardNotFound, + + /// Occurs when bot tries to delete a message which does not exists. + /// + /// May happen in methods: + /// 1. [`DeleteMessage`] + /// + /// [`DeleteMessage`]: crate::requests::DeleteMessage + #[serde(rename = "Bad Request: message to delete not found")] + MessageToDeleteNotFound, + + /// Occurs when bot tries to send a text message without text. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: message text is empty")] + MessageTextIsEmpty, + + /// Occurs when bot tries to edit a message after long time. + /// + /// May happen in methods: + /// 1. [`EditMessageText`] + /// + /// [`EditMessageText`]: crate::requests::EditMessageText + #[serde(rename = "Bad Request: message can't be edited")] + MessageCantBeEdited, + + /// Occurs when bot tries to delete a someone else's message in group where + /// it does not have enough rights. + /// + /// May happen in methods: + /// 1. [`DeleteMessage`] + /// + /// [`DeleteMessage`]: crate::requests::DeleteMessage + #[serde(rename = "Bad Request: message can't be deleted")] + MessageCantBeDeleted, + + /// Occurs when bot tries to edit a message which does not exists. + /// + /// May happen in methods: + /// 1. [`EditMessageText`] + /// + /// [`EditMessageText`]: crate::requests::EditMessageText + #[serde(rename = "Bad Request: message to edit not found")] + MessageToEditNotFound, + + /// Occurs when bot tries to reply to a message which does not exists. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: reply message not found")] + MessageToReplyNotFound, + + /// Occurs when bot tries to + #[serde(rename = "Bad Request: message identifier is not specified")] + MessageIdentifierNotSpecified, + + /// Occurs when bot tries to send a message with text size greater then + /// 4096 symbols. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: message is too long")] + MessageIsTooLong, + + /// Occurs when bot tries to send media group with more than 10 items. + /// + /// May happen in methods: + /// 1. [`SendMediaGroup`] + /// + /// [`SendMediaGroup`]: crate::requests::SendMediaGroup + #[serde(rename = "Bad Request: Too much messages to send as an album")] + ToMuchMessages, + + /// Occurs when bot tries to stop poll that has already been stopped. + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll has already been closed")] + PollHasAlreadyClosed, + + /// Occurs when bot tries to send poll with less than 2 options. + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll must have at least 2 option")] + PollMustHaveMoreOptions, + + /// Occurs when bot tries to send poll with more than 10 options. + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll can't have more than 10 options")] + PollCantHaveMoreOptions, + + /// Occurs when bot tries to send poll with empty option (without text). + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll options must be non-empty")] + PollOptionsMustBeNonEmpty, + + /// Occurs when bot tries to send poll with empty question (without text). + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll question must be non-empty")] + PollQuestionMustBeNonEmpty, + + /// Occurs when bot tries to send poll with total size of options more than + /// 100 symbols. + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll options length must not exceed 100")] + PollOptionsLengthTooLong, + + /// Occurs when bot tries to send poll with question size more than 255 + /// symbols. + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll question length must not exceed 255")] + PollQuestionLengthTooLong, + + /// Occurs when bot tries to stop poll with message without poll. + /// + /// May happen in methods: + /// 1. [`StopPoll`] + /// + /// [`StopPoll`]: crate::requests::StopPoll + #[serde(rename = "Bad Request: message with poll to stop not found")] + MessageWithPollNotFound, + + /// Occurs when bot tries to stop poll with message without poll. + /// + /// May happen in methods: + /// 1. [`StopPoll`] + /// + /// [`StopPoll`]: crate::requests::StopPoll + #[serde(rename = "Bad Request: message is not a poll")] + MessageIsNotAPoll, + + /// Occurs when bot tries to send a message to chat in which it is not a + /// member. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: chat not found")] + ChatNotFound, + + /// Occurs when bot tries to send method with unknown user_id. + /// + /// May happen in methods: + /// 1. [`getUserProfilePhotos`] + /// + /// [`getUserProfilePhotos`]: + /// crate::requests::GetUserProfilePhotos + #[serde(rename = "Bad Request: user not found")] + UserNotFound, + + /// Occurs when bot tries to send [`SetChatDescription`] with same text as + /// in the current description. + /// + /// May happen in methods: + /// 1. [`SetChatDescription`] + /// + /// [`SetChatDescription`]: crate::requests::SetChatDescription + #[serde(rename = "Bad Request: chat description is not modified")] + ChatDescriptionIsNotModified, + + /// Occurs when bot tries to answer to query after timeout expire. + /// + /// May happen in methods: + /// 1. [`AnswerCallbackQuery`] + /// + /// [`AnswerCallbackQuery`]: crate::requests::AnswerCallbackQuery + #[serde(rename = "Bad Request: query is too old and response timeout expired or query id is \ + invalid")] + InvalidQueryID, + + /// Occurs when bot tries to send InlineKeyboardMarkup with invalid button + /// url. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: BUTTON_URL_INVALID")] + ButtonURLInvalid, + + /// Occurs when bot tries to send button with data size more than 64 bytes. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: BUTTON_DATA_INVALID")] + ButtonDataInvalid, + + /// Occurs when bot tries to send button with data size == 0. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: can't parse inline keyboard button: Text buttons are \ + unallowed in the inline keyboard")] + TextButtonsAreUnallowed, + + /// Occurs when bot tries to get file by wrong file id. + /// + /// May happen in methods: + /// 1. [`GetFile`] + /// + /// [`GetFile`]: crate::requests::GetFile + #[serde(rename = "Bad Request: wrong file id")] + WrongFileID, + + /// Occurs when bot tries to do some with group which was deactivated. + #[serde(rename = "Bad Request: group is deactivated")] + GroupDeactivated, + + /// Occurs when bot tries to set chat photo from file ID + /// + /// May happen in methods: + /// 1. [`SetChatPhoto`] + /// + /// [`SetChatPhoto`]: crate::requests::SetChatPhoto + #[serde(rename = "Bad Request: Photo should be uploaded as an InputFile")] + PhotoAsInputFileRequired, + + /// Occurs when bot tries to add sticker to stickerset by invalid name. + /// + /// May happen in methods: + /// 1. [`AddStickerToSet`] + /// + /// [`AddStickerToSet`]: crate::requests::AddStickerToSet + #[serde(rename = "Bad Request: STICKERSET_INVALID")] + InvalidStickersSet, + + /// Occurs when bot tries to pin a message without rights to pin in this + /// chat. + /// + /// May happen in methods: + /// 1. [`PinChatMessage`] + /// + /// [`PinChatMessage`]: crate::requests::PinChatMessage + #[serde(rename = "Bad Request: not enough rights to pin a message")] + NotEnoughRightsToPinMessage, + + /// Occurs when bot tries to use method in group which is allowed only in a + /// supergroup or channel. + #[serde(rename = "Bad Request: method is available only for supergroups and channel")] + MethodNotAvailableInPrivateChats, + + /// Occurs when bot tries to demote chat creator. + /// + /// May happen in methods: + /// 1. [`PromoteChatMember`] + /// + /// [`PromoteChatMember`]: crate::requests::PromoteChatMember + #[serde(rename = "Bad Request: can't demote chat creator")] + CantDemoteChatCreator, + + /// Occurs when bot tries to restrict self in group chats. + /// + /// May happen in methods: + /// 1. [`RestrictChatMember`] + /// + /// [`RestrictChatMember`]: crate::requests::RestrictChatMember + #[serde(rename = "Bad Request: can't restrict self")] + CantRestrictSelf, + + /// Occurs when bot tries to restrict chat member without rights to + /// restrict in this chat. + /// + /// May happen in methods: + /// 1. [`RestrictChatMember`] + /// + /// [`RestrictChatMember`]: crate::requests::RestrictChatMember + #[serde(rename = "Bad Request: not enough rights to restrict/unrestrict chat member")] + NotEnoughRightsToRestrict, + + /// Occurs when bot tries set webhook to protocol other than HTTPS. + /// + /// May happen in methods: + /// 1. [`SetWebhook`] + /// + /// [`SetWebhook`]: crate::requests::SetWebhook + #[serde(rename = "Bad Request: bad webhook: HTTPS url must be provided for webhook")] + WebhookRequireHTTPS, + + /// Occurs when bot tries to set webhook to port other than 80, 88, 443 or + /// 8443. + /// + /// May happen in methods: + /// 1. [`SetWebhook`] + /// + /// [`SetWebhook`]: crate::requests::SetWebhook + #[serde(rename = "Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 \ + or 8443")] + BadWebhookPort, + + /// Occurs when bot tries to set webhook to unknown host. + /// + /// May happen in methods: + /// 1. [`SetWebhook`] + /// + /// [`SetWebhook`]: crate::requests::SetWebhook + #[serde(rename = "Bad Request: bad webhook: Failed to resolve host: Name or service not known")] + UnknownHost, + + /// Occurs when bot tries to set webhook to invalid URL. + /// + /// May happen in methods: + /// 1. [`SetWebhook`] + /// + /// [`SetWebhook`]: crate::requests::SetWebhook + #[serde(rename = "Bad Request: can't parse URL")] + CantParseUrl, + + /// Occurs when bot tries to send message with unfinished entities. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: can't parse entities")] + CantParseEntities, + + /// Occurs when bot tries to use getUpdates while webhook is active. + /// + /// May happen in methods: + /// 1. [`GetUpdates`] + /// + /// [`GetUpdates`]: crate::requests::GetUpdates + #[serde(rename = "can't use getUpdates method while webhook is active")] + CantGetUpdates, + + /// Occurs when bot tries to do some in group where bot was kicked. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Unauthorized: bot was kicked from a chat")] + BotKicked, + + /// Occurs when bot tries to send message to deactivated user. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Unauthorized: user is deactivated")] + UserDeactivated, + + /// Occurs when you tries to initiate conversation with a user. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Unauthorized: bot can't initiate conversation with a user")] + CantInitiateConversation, + + /// Occurs when you tries to send message to bot. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Unauthorized: bot can't send messages to bots")] + CantTalkWithBots, + + /// Occurs when bot tries to send button with invalid http url. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: wrong HTTP URL")] + WrongHTTPurl, + + /// Occurs when bot tries GetUpdate before the timeout. Make sure that only + /// one Updater is running. + /// + /// May happen in methods: + /// 1. [`GetUpdates`] + /// + /// [`GetUpdates`]: crate::requests::GetUpdates + #[serde(rename = "Conflict: terminated by other getUpdates request; make sure that only one \ + bot instance is running")] + TerminatedByOtherGetUpdates, + + /// Occurs when bot tries to get file by invalid file id. + /// + /// May happen in methods: + /// 1. [`GetFile`] + /// + /// [`GetFile`]: crate::requests::GetFile + #[serde(rename = "Bad Request: invalid file id")] + FileIdInvalid, +} diff --git a/src/lib.rs b/src/lib.rs index 1adb08cb..27ddfcd6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,4 +9,45 @@ // ``` #![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg))] #![forbid(unsafe_code)] -#![deny(missing_docs)] +//#![deny(missing_docs)] + +pub use self::{ + bot::{Bot, BotBuilder}, + errors::{ApiErrorKind, DownloadError, KnownApiErrorKind, RequestError}, +}; + +pub mod requests; +pub mod types; + +// reexported +mod bot; +mod errors; + +// implementation details +mod net; + +/// Constructs a client from the `TELOXIDE_PROXY` environmental variable. +/// +/// This function passes the value of `TELOXIDE_PROXY` into +/// [`reqwest::Proxy::all`], if it exists, otherwise returns the default +/// client. +/// +/// # Note +/// The created client will have safe settings, meaning that it will be able to +/// work in long time durations, see the [issue 223]. +/// +/// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all +/// [issue 223]: https://github.com/teloxide/teloxide/issues/223 +pub fn client_from_env() -> reqwest::Client { + use crate::bot::{sound_bot, TELOXIDE_PROXY}; + use reqwest::Proxy; + + let builder = sound_bot(); + + match std::env::var(TELOXIDE_PROXY).ok() { + Some(proxy) => builder.proxy(Proxy::all(&proxy).expect("creating reqwest::Proxy")), + None => builder, + } + .build() + .expect("creating reqwest::Client") +} diff --git a/src/net/download.rs b/src/net/download.rs new file mode 100644 index 00000000..05f08f5d --- /dev/null +++ b/src/net/download.rs @@ -0,0 +1,49 @@ +use reqwest::Client; +use tokio::io::{AsyncWrite, AsyncWriteExt}; + +use crate::errors::DownloadError; + +use super::TELEGRAM_API_URL; + +pub async fn download_file( + client: &Client, + token: &str, + path: &str, + destination: &mut D, +) -> Result<(), DownloadError> +where + D: AsyncWrite + Unpin, +{ + let mut res = client + .get(&super::file_url(TELEGRAM_API_URL, token, path)) + .send() + .await? + .error_for_status()?; + + while let Some(chunk) = res.chunk().await? { + destination.write_all(&chunk).await?; + } + + Ok(()) +} + +#[cfg(feature = "unstable-stream")] +pub async fn download_file_stream( + client: &Client, + token: &str, + path: &str, +) -> Result>, reqwest::Error> { + let res = client + .get(&super::file_url(TELEGRAM_API_URL, token, path)) + .send() + .await? + .error_for_status()?; + + Ok(futures::stream::unfold(res, |mut res| async { + match res.chunk().await { + Err(err) => Some((Err(err), res)), + Ok(Some(c)) => Some((Ok(c), res)), + Ok(None) => None, + } + })) +} diff --git a/src/net/mod.rs b/src/net/mod.rs new file mode 100644 index 00000000..b4944959 --- /dev/null +++ b/src/net/mod.rs @@ -0,0 +1,61 @@ +#[cfg(feature = "unstable-stream")] +pub use download::download_file_stream; + +pub use self::{ + download::download_file, + request::{request_json, request_multipart}, + telegram_response::TelegramResponse, +}; + +mod download; +mod request; +mod telegram_response; + +const TELEGRAM_API_URL: &str = "https://api.telegram.org"; + +/// Creates URL for making HTTPS requests. See the [Telegram documentation]. +/// +/// [Telegram documentation]: https://core.telegram.org/bots/api#making-requests +fn method_url(base: &str, token: &str, method_name: &str) -> String { + format!("{url}/bot{token}/{method}", url = base, token = token, method = method_name,) +} + +/// Creates URL for downloading a file. See the [Telegram documentation]. +/// +/// [Telegram documentation]: https://core.telegram.org/bots/api#file +fn file_url(base: &str, token: &str, file_path: &str) -> String { + format!("{url}/file/bot{token}/{file}", url = base, token = token, file = file_path,) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn method_url_test() { + let url = method_url( + TELEGRAM_API_URL, + "535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao", + "methodName", + ); + + assert_eq!( + url, + "https://api.telegram.org/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/methodName" + ); + } + + #[test] + fn file_url_test() { + let url = file_url( + TELEGRAM_API_URL, + "535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao", + "AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ", + ); + + assert_eq!( + url, + "https://api.telegram.org/file/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ" + ); + } +} diff --git a/src/net/request.rs b/src/net/request.rs new file mode 100644 index 00000000..e376c3fd --- /dev/null +++ b/src/net/request.rs @@ -0,0 +1,63 @@ +use reqwest::{multipart::Form, Client, Response}; +use serde::{de::DeserializeOwned, Serialize}; + +use crate::{requests::ResponseResult, RequestError}; + +use super::{TelegramResponse, TELEGRAM_API_URL}; +use std::time::Duration; + +const DELAY_ON_SERVER_ERROR: Duration = Duration::from_secs(10); + +pub async fn request_multipart( + client: &Client, + token: &str, + method_name: &str, + params: Form, +) -> ResponseResult +where + T: DeserializeOwned, +{ + let response = client + .post(&super::method_url(TELEGRAM_API_URL, token, method_name)) + .multipart(params) + .send() + .await + .map_err(RequestError::NetworkError)?; + + process_response(response).await +} + +pub async fn request_json( + client: &Client, + token: &str, + method_name: &str, + params: &P, +) -> ResponseResult +where + T: DeserializeOwned, + P: Serialize, +{ + let response = client + .post(&super::method_url(TELEGRAM_API_URL, token, method_name)) + .json(params) + .send() + .await + .map_err(RequestError::NetworkError)?; + + process_response(response).await +} + +async fn process_response(response: Response) -> ResponseResult +where + T: DeserializeOwned, +{ + if response.status().is_server_error() { + tokio::time::delay_for(DELAY_ON_SERVER_ERROR).await; + } + + serde_json::from_str::>( + &response.text().await.map_err(RequestError::NetworkError)?, + ) + .map_err(RequestError::InvalidJson)? + .into() +} diff --git a/src/net/telegram_response.rs b/src/net/telegram_response.rs new file mode 100644 index 00000000..d5062e8f --- /dev/null +++ b/src/net/telegram_response.rs @@ -0,0 +1,70 @@ +use reqwest::StatusCode; +use serde::Deserialize; + +use crate::{ + requests::ResponseResult, + types::{False, ResponseParameters, True}, + ApiErrorKind, RequestError, +}; + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum TelegramResponse { + Ok { + /// A dummy field. Used only for deserialization. + #[allow(dead_code)] + ok: True, + + result: R, + }, + Err { + /// A dummy field. Used only for deserialization. + #[allow(dead_code)] + ok: False, + + #[serde(rename = "description")] + kind: ApiErrorKind, + error_code: u16, + response_parameters: Option, + }, +} + +impl Into> for TelegramResponse { + fn into(self) -> Result { + match self { + TelegramResponse::Ok { result, .. } => Ok(result), + TelegramResponse::Err { kind, error_code, response_parameters, .. } => { + if let Some(params) = response_parameters { + match params { + ResponseParameters::RetryAfter(i) => Err(RequestError::RetryAfter(i)), + ResponseParameters::MigrateToChatId(to) => { + Err(RequestError::MigrateToChatId(to)) + } + } + } else { + Err(RequestError::ApiError { + kind, + status_code: StatusCode::from_u16(error_code).unwrap(), + }) + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{errors::KnownApiErrorKind, types::Update}; + + #[test] + fn terminated_by_other_get_updates() { + let expected = ApiErrorKind::Known(KnownApiErrorKind::TerminatedByOtherGetUpdates); + if let TelegramResponse::Err{ kind, .. } = serde_json::from_str::>(r#"{"ok":false,"error_code":409,"description":"Conflict: terminated by other getUpdates request; make sure that only one bot instance is running"}"#).unwrap() { + assert_eq!(expected, kind); + } + else { + panic!("Expected ApiErrorKind::TerminatedByOtherGetUpdates"); + } + } +} diff --git a/src/requests/all/add_sticker_to_set.rs b/src/requests/all/add_sticker_to_set.rs new file mode 100644 index 00000000..8cc0188f --- /dev/null +++ b/src/requests/all/add_sticker_to_set.rs @@ -0,0 +1,109 @@ +use crate::{ + net, + requests::form_builder::FormBuilder, + types::{MaskPosition, True}, + Bot, +}; + +use crate::{ + requests::{RequestWithFile, ResponseResult}, + types::StickerType, +}; + +/// 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(Debug, Clone)] +pub struct AddStickerToSet { + bot: Bot, + user_id: i32, + name: String, + sticker_type: StickerType, + emojis: String, + mask_position: Option, +} + +#[async_trait::async_trait] +impl RequestWithFile for AddStickerToSet { + type Output = True; + + async fn send(&self) -> tokio::io::Result> { + let builder = + FormBuilder::new().add_text("user_id", &self.user_id).add_text("name", &self.name); + + let builder = match &self.sticker_type { + StickerType::Png(file) => builder.add_input_file("png_sticker", &file), + StickerType::Tgs(file) => builder.add_input_file("tgs_sticker", &file), + } + .await? + .add_text("emojis", &self.emojis) + .add_text("mask_position", &self.mask_position); + + Ok(net::request_multipart( + self.bot.client(), + self.bot.token(), + "addStickerToSet", + builder.build(), + ) + .await) + } +} + +impl AddStickerToSet { + pub(crate) fn new( + bot: Bot, + user_id: i32, + name: N, + sticker_type: StickerType, + emojis: E, + ) -> Self + where + N: Into, + E: Into, + { + Self { + bot, + user_id, + name: name.into(), + sticker_type, + emojis: emojis.into(), + mask_position: None, + } + } + + /// User identifier of sticker set owner. + pub fn user_id(mut self, val: i32) -> Self { + self.user_id = val; + self + } + + /// Sticker set name. + pub fn name(mut self, val: T) -> Self + where + T: Into, + { + self.name = val.into(); + self + } + + pub fn sticker_type(mut self, val: StickerType) -> Self { + self.sticker_type = val; + self + } + + /// One or more emoji corresponding to the sticker. + pub fn emojis(mut self, val: T) -> Self + where + T: Into, + { + self.emojis = val.into(); + self + } + + /// A JSON-serialized object for position where the mask should be placed on + /// faces. + pub fn mask_position(mut self, val: MaskPosition) -> Self { + self.mask_position = Some(val); + self + } +} diff --git a/src/requests/all/answer_callback_query.rs b/src/requests/all/answer_callback_query.rs new file mode 100644 index 00000000..c74b1e88 --- /dev/null +++ b/src/requests/all/answer_callback_query.rs @@ -0,0 +1,101 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::True, + Bot, +}; + +/// Use this method to send answers to callback queries sent from [inline +/// keyboards]. +/// +/// The answer will be displayed to the user as a notification at +/// the top of the chat screen or as an alert. +/// +/// [The official docs](https://core.telegram.org/bots/api#answercallbackquery). +/// +/// [inline keyboards]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct AnswerCallbackQuery { + #[serde(skip_serializing)] + bot: Bot, + callback_query_id: String, + text: Option, + show_alert: Option, + url: Option, + cache_time: Option, +} + +#[async_trait::async_trait] +impl Request for AnswerCallbackQuery { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "answerCallbackQuery", &self).await + } +} + +impl AnswerCallbackQuery { + pub(crate) fn new(bot: Bot, callback_query_id: C) -> Self + where + C: Into, + { + let callback_query_id = callback_query_id.into(); + Self { bot, callback_query_id, text: None, show_alert: None, url: None, cache_time: None } + } + + /// Unique identifier for the query to be answered. + pub fn callback_query_id(mut self, val: T) -> Self + where + T: Into, + { + self.callback_query_id = val.into(); + self + } + + /// Text of the notification. If not specified, nothing will be shown to the + /// user, 0-200 characters. + pub fn text(mut self, val: T) -> Self + where + T: Into, + { + self.text = Some(val.into()); + self + } + + /// If `true`, an alert will be shown by the client instead of a + /// notification at the top of the chat screen. Defaults to `false`. + pub fn show_alert(mut self, val: bool) -> Self { + self.show_alert = Some(val); + self + } + + /// URL that will be opened by the user's client. If you have created a + /// [`Game`] and accepted the conditions via [@Botfather], specify the + /// URL that opens your game – note that this will only work if the + /// query comes from a [`callback_game`] button. + /// + /// Otherwise, you may use links like `t.me/your_bot?start=XXXX` that open + /// your bot with a parameter. + /// + /// [@Botfather]: https://t.me/botfather + /// [`callback_game`]: crate::types::InlineKeyboardButton + /// [`Game`]: crate::types::Game + pub fn url(mut self, val: T) -> Self + where + T: Into, + { + self.url = Some(val.into()); + self + } + + /// The maximum amount of time in seconds that the result of the callback + /// query may be cached client-side. Telegram apps will support caching + /// starting in version 3.14. Defaults to 0. + pub fn cache_time(mut self, val: i32) -> Self { + self.cache_time = Some(val); + self + } +} diff --git a/src/requests/all/answer_inline_query.rs b/src/requests/all/answer_inline_query.rs new file mode 100644 index 00000000..dd1b6a38 --- /dev/null +++ b/src/requests/all/answer_inline_query.rs @@ -0,0 +1,146 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{InlineQueryResult, True}, + Bot, +}; + +/// Use this method to send answers to an inline query. +/// +/// No more than **50** results per query are allowed. +/// +/// [The official docs](https://core.telegram.org/bots/api#answerinlinequery). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct AnswerInlineQuery { + #[serde(skip_serializing)] + bot: Bot, + inline_query_id: String, + results: Vec, + cache_time: Option, + is_personal: Option, + next_offset: Option, + switch_pm_text: Option, + switch_pm_parameter: Option, +} + +#[async_trait::async_trait] +impl Request for AnswerInlineQuery { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "answerInlineQuery", &self).await + } +} + +impl AnswerInlineQuery { + pub(crate) fn new(bot: Bot, inline_query_id: I, results: R) -> Self + where + I: Into, + R: Into>, + { + let inline_query_id = inline_query_id.into(); + let results = results.into(); + Self { + bot, + inline_query_id, + results, + cache_time: None, + is_personal: None, + next_offset: None, + switch_pm_text: None, + switch_pm_parameter: None, + } + } + + /// Unique identifier for the answered query. + pub fn inline_query_id(mut self, val: T) -> Self + where + T: Into, + { + self.inline_query_id = val.into(); + self + } + + /// A JSON-serialized array of results for the inline query. + pub fn results(mut self, val: T) -> Self + where + T: Into>, + { + self.results = val.into(); + self + } + + /// The maximum amount of time in seconds that the result of the inline + /// query may be cached on the server. + /// + /// Defaults to 300. + pub fn cache_time(mut self, val: i32) -> Self { + self.cache_time = Some(val); + self + } + + /// Pass `true`, if results may be cached on the server side only for the + /// user that sent the query. + /// + /// By default, results may be returned to any user who sends the same + /// query. + #[allow(clippy::wrong_self_convention)] + pub fn is_personal(mut self, val: bool) -> Self { + self.is_personal = Some(val); + self + } + + /// Pass the offset that a client should send in the next query with the + /// same text to receive more results. + /// + /// Pass an empty string if there are no more results or if you don‘t + /// support pagination. Offset length can’t exceed 64 bytes. + pub fn next_offset(mut self, val: T) -> Self + where + T: Into, + { + self.next_offset = Some(val.into()); + self + } + + /// If passed, clients will display a button with specified text that + /// switches the user to a private chat with the bot and sends the bot a + /// start message with the parameter [`switch_pm_parameter`]. + /// + /// [`switch_pm_parameter`]: + /// crate::requests::AnswerInlineQuery::switch_pm_parameter + pub fn switch_pm_text(mut self, val: T) -> Self + where + T: Into, + { + self.switch_pm_text = Some(val.into()); + self + } + + /// [Deep-linking] parameter for the /start message sent to the bot when + /// user presses the switch button. 1-64 characters, only `A-Z`, `a-z`, + /// `0-9`, `_` and `-` are allowed. + /// + /// Example: An inline bot that sends YouTube videos can ask the user to + /// connect the bot to their YouTube account to adapt search results + /// accordingly. To do this, it displays a ‘Connect your YouTube account’ + /// button above the results, or even before showing any. The user presses + /// the button, switches to a private chat with the bot and, in doing so, + /// passes a start parameter that instructs the bot to return an oauth link. + /// Once done, the bot can offer a [`switch_inline`] button so that the user + /// can easily return to the chat where they wanted to use the bot's + /// inline capabilities. + /// + /// [Deep-linking]: https://core.telegram.org/bots#deep-linking + /// [`switch_inline`]: crate::types::InlineKeyboardMarkup + pub fn switch_pm_parameter(mut self, val: T) -> Self + where + T: Into, + { + self.switch_pm_parameter = Some(val.into()); + self + } +} diff --git a/src/requests/all/answer_pre_checkout_query.rs b/src/requests/all/answer_pre_checkout_query.rs new file mode 100644 index 00000000..2ac4df42 --- /dev/null +++ b/src/requests/all/answer_pre_checkout_query.rs @@ -0,0 +1,82 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::True, + Bot, +}; + +/// 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 +/// `pre_checkout_query`. Use this method to respond to such pre-checkout +/// queries. +/// +/// # Note +/// The Bot API must receive an answer within 10 seconds after the pre-checkout +/// query was sent. +/// +/// [The official docs](https://core.telegram.org/bots/api#answerprecheckoutquery). +/// +/// [`Update`]: crate::types::Update +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct AnswerPreCheckoutQuery { + #[serde(skip_serializing)] + bot: Bot, + pre_checkout_query_id: String, + ok: bool, + error_message: Option, +} + +#[async_trait::async_trait] +impl Request for AnswerPreCheckoutQuery { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "answerPreCheckoutQuery", &self) + .await + } +} + +impl AnswerPreCheckoutQuery { + pub(crate) fn new

(bot: Bot, pre_checkout_query_id: P, ok: bool) -> Self + where + P: Into, + { + let pre_checkout_query_id = pre_checkout_query_id.into(); + Self { bot, pre_checkout_query_id, ok, error_message: None } + } + + /// Unique identifier for the query to be answered. + pub fn pre_checkout_query_id(mut self, val: T) -> Self + where + T: Into, + { + self.pre_checkout_query_id = val.into(); + self + } + + /// 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. + pub fn ok(mut self, val: bool) -> Self { + self.ok = val; + self + } + + /// Required if ok is `false`. Error message in human readable form that + /// explains the reason for failure to proceed with the checkout (e.g. + /// "Sorry, somebody just bought the last of our amazing black T-shirts + /// while you were busy filling out your payment details. Please choose a + /// different color or garment!"). + /// + /// Telegram will display this message to the user. + pub fn error_message(mut self, val: T) -> Self + where + T: Into, + { + self.error_message = Some(val.into()); + self + } +} diff --git a/src/requests/all/answer_shipping_query.rs b/src/requests/all/answer_shipping_query.rs new file mode 100644 index 00000000..be4d6662 --- /dev/null +++ b/src/requests/all/answer_shipping_query.rs @@ -0,0 +1,86 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ShippingOption, True}, + Bot, +}; + +/// 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 +/// queries. +/// +/// [The official docs](https://core.telegram.org/bots/api#answershippingquery). +/// +/// [`Update`]: crate::types::Update +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct AnswerShippingQuery { + #[serde(skip_serializing)] + bot: Bot, + shipping_query_id: String, + ok: bool, + shipping_options: Option>, + error_message: Option, +} + +#[async_trait::async_trait] +impl Request for AnswerShippingQuery { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "answerShippingQuery", &self).await + } +} + +impl AnswerShippingQuery { + pub(crate) fn new(bot: Bot, shipping_query_id: S, ok: bool) -> Self + where + S: Into, + { + let shipping_query_id = shipping_query_id.into(); + Self { bot, shipping_query_id, ok, shipping_options: None, error_message: None } + } + + /// Unique identifier for the query to be answered. + pub fn shipping_query_id(mut self, val: T) -> Self + where + T: Into, + { + self.shipping_query_id = val.into(); + self + } + + /// Specify `true` if delivery to the specified address is possible and + /// `false` if there are any problems (for example, if delivery to the + /// specified address is not possible). + pub fn ok(mut self, val: bool) -> Self { + self.ok = val; + self + } + + /// Required if ok is `true`. A JSON-serialized array of available shipping + /// options. + pub fn shipping_options(mut self, val: T) -> Self + where + T: Into>, + { + self.shipping_options = Some(val.into()); + self + } + + /// Required if ok is `false`. Error message in human readable form that + /// explains why it is impossible to complete the order (e.g. "Sorry, + /// delivery to your desired address is unavailable'). + /// + /// Telegram will display this message to the user. + pub fn error_message(mut self, val: T) -> Self + where + T: Into, + { + self.error_message = Some(val.into()); + self + } +} diff --git a/src/requests/all/create_new_sticker_set.rs b/src/requests/all/create_new_sticker_set.rs new file mode 100644 index 00000000..a49cb08b --- /dev/null +++ b/src/requests/all/create_new_sticker_set.rs @@ -0,0 +1,134 @@ +use crate::{ + net, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + types::{MaskPosition, StickerType, True}, + Bot, +}; + +/// 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(Debug, Clone)] +pub struct CreateNewStickerSet { + bot: Bot, + user_id: i32, + name: String, + title: String, + sticker_type: StickerType, + emojis: String, + contains_masks: Option, + mask_position: Option, +} + +#[async_trait::async_trait] +impl RequestWithFile for CreateNewStickerSet { + type Output = True; + + async fn send(&self) -> tokio::io::Result> { + let builder = FormBuilder::new() + .add_text("user_id", &self.user_id) + .add_text("name", &self.name) + .add_text("title", &self.title); + + let builder = match &self.sticker_type { + StickerType::Png(file) => builder.add_input_file("png_sticker", &file), + StickerType::Tgs(file) => builder.add_input_file("tgs_sticker", &file), + } + .await? + .add_text("emojis", &self.emojis) + .add_text("contains_masks", &self.contains_masks) + .add_text("mask_position", &self.mask_position); + + Ok(net::request_multipart( + self.bot.client(), + self.bot.token(), + "createNewStickerSet", + builder.build(), + ) + .await) + } +} + +impl CreateNewStickerSet { + pub(crate) fn new( + bot: Bot, + user_id: i32, + name: N, + title: T, + sticker_type: StickerType, + emojis: E, + ) -> Self + where + N: Into, + T: Into, + E: Into, + { + Self { + bot, + user_id, + name: name.into(), + title: title.into(), + sticker_type, + emojis: emojis.into(), + contains_masks: None, + mask_position: None, + } + } + + /// User identifier of created sticker set owner. + pub fn user_id(mut self, val: i32) -> Self { + self.user_id = val; + self + } + + /// Short name of sticker set, to be used in `t.me/addstickers/` URLs (e.g., + /// animals). Can contain only english letters, digits and underscores. + /// + /// Must begin with a letter, can't contain consecutive underscores and must + /// end in `_by_`. `` is case insensitive. + /// 1-64 characters. + pub fn name(mut self, val: T) -> Self + where + T: Into, + { + self.name = val.into(); + self + } + + /// Sticker set title, 1-64 characters. + pub fn title(mut self, val: T) -> Self + where + T: Into, + { + self.title = val.into(); + self + } + + pub fn sticker_type(mut self, val: StickerType) -> Self { + self.sticker_type = val; + self + } + + /// One or more emoji corresponding to the sticker. + pub fn emojis(mut self, val: T) -> Self + where + T: Into, + { + self.emojis = val.into(); + self + } + + /// Pass `true`, if a set of mask stickers should be created. + pub fn contains_masks(mut self, val: bool) -> Self { + self.contains_masks = Some(val); + self + } + + /// A JSON-serialized object for position where the mask should be placed on + /// faces. + pub fn mask_position(mut self, val: MaskPosition) -> Self { + self.mask_position = Some(val); + self + } +} diff --git a/src/requests/all/delete_chat_photo.rs b/src/requests/all/delete_chat_photo.rs new file mode 100644 index 00000000..01439bfa --- /dev/null +++ b/src/requests/all/delete_chat_photo.rs @@ -0,0 +1,50 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, True}, + Bot, +}; + +/// 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 +/// must have the appropriate admin rights. +/// +/// [The official docs](https://core.telegram.org/bots/api#deletechatphoto). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct DeleteChatPhoto { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, +} + +#[async_trait::async_trait] +impl Request for DeleteChatPhoto { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "deleteChatPhoto", &self).await + } +} + +impl DeleteChatPhoto { + pub(crate) fn new(bot: Bot, chat_id: C) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } +} diff --git a/src/requests/all/delete_chat_sticker_set.rs b/src/requests/all/delete_chat_sticker_set.rs new file mode 100644 index 00000000..9fe228c5 --- /dev/null +++ b/src/requests/all/delete_chat_sticker_set.rs @@ -0,0 +1,55 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, True}, + Bot, +}; + +/// Use this method to delete a group sticker set from a supergroup. +/// +/// The bot must be an administrator in the chat for this to work and must have +/// the appropriate admin rights. Use the field `can_set_sticker_set` optionally +/// returned in [`Bot::get_chat`] requests to check if the bot can use this +/// method. +/// +/// [The official docs](https://core.telegram.org/bots/api#deletechatstickerset). +/// +/// [`Bot::get_chat`]: crate::Bot::get_chat +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct DeleteChatStickerSet { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, +} + +#[async_trait::async_trait] +impl Request for DeleteChatStickerSet { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "deleteChatStickerSet", &self).await + } +} + +impl DeleteChatStickerSet { + pub(crate) fn new(bot: Bot, chat_id: C) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id } + } + + /// Unique identifier for the target chat or username of the target + /// supergroup (in the format `@supergroupusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } +} diff --git a/src/requests/all/delete_message.rs b/src/requests/all/delete_message.rs new file mode 100644 index 00000000..35ead60a --- /dev/null +++ b/src/requests/all/delete_message.rs @@ -0,0 +1,67 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, True}, + Bot, +}; + +/// Use this method to delete a message, including service messages. +/// +/// The limitations are: +/// - A message can only be deleted if it was sent less than 48 hours ago. +/// - Bots can delete outgoing messages in private chats, groups, and +/// supergroups. +/// - Bots can delete incoming messages in private chats. +/// - Bots granted can_post_messages permissions can delete outgoing messages +/// in channels. +/// - If the bot is an administrator of a group, it can delete any message +/// there. +/// - If the bot has can_delete_messages permission in a supergroup or a +/// channel, it can delete any message there. +/// +/// [The official docs](https://core.telegram.org/bots/api#deletemessage). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct DeleteMessage { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + message_id: i32, +} + +#[async_trait::async_trait] +impl Request for DeleteMessage { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "deleteMessage", &self).await + } +} + +impl DeleteMessage { + pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id, message_id } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Identifier of the message to delete. + pub fn message_id(mut self, val: i32) -> Self { + self.message_id = val; + self + } +} diff --git a/src/requests/all/delete_sticker_from_set.rs b/src/requests/all/delete_sticker_from_set.rs new file mode 100644 index 00000000..59269f7a --- /dev/null +++ b/src/requests/all/delete_sticker_from_set.rs @@ -0,0 +1,47 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::True, + Bot, +}; + +/// 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(Debug, Clone, Serialize)] +pub struct DeleteStickerFromSet { + #[serde(skip_serializing)] + bot: Bot, + sticker: String, +} + +#[async_trait::async_trait] +impl Request for DeleteStickerFromSet { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "deleteStickerFromSet", &self).await + } +} + +impl DeleteStickerFromSet { + pub(crate) fn new(bot: Bot, sticker: S) -> Self + where + S: Into, + { + let sticker = sticker.into(); + Self { bot, sticker } + } + + /// File identifier of the sticker. + pub fn sticker(mut self, val: T) -> Self + where + T: Into, + { + self.sticker = val.into(); + self + } +} diff --git a/src/requests/all/delete_webhook.rs b/src/requests/all/delete_webhook.rs new file mode 100644 index 00000000..fd3bd3ba --- /dev/null +++ b/src/requests/all/delete_webhook.rs @@ -0,0 +1,37 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::True, + Bot, +}; + +/// Use this method to remove webhook integration if you decide to switch back +/// to [Bot::get_updates]. +/// +/// [The official docs](https://core.telegram.org/bots/api#deletewebhook). +/// +/// [Bot::get_updates]: crate::Bot::get_updates +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct DeleteWebhook { + #[serde(skip_serializing)] + bot: Bot, +} + +#[async_trait::async_trait] +impl Request for DeleteWebhook { + type Output = True; + + #[allow(clippy::trivially_copy_pass_by_ref)] + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "deleteWebhook", &self).await + } +} + +impl DeleteWebhook { + pub(crate) fn new(bot: Bot) -> Self { + Self { bot } + } +} diff --git a/src/requests/all/edit_inline_message_caption.rs b/src/requests/all/edit_inline_message_caption.rs new file mode 100644 index 00000000..e777f17c --- /dev/null +++ b/src/requests/all/edit_inline_message_caption.rs @@ -0,0 +1,83 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{InlineKeyboardMarkup, ParseMode, True}, + Bot, +}; + +/// Use this method to edit captions of messages sent via the bot. +/// +/// On success, [`True`] is returned. +/// +/// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). +/// +/// [`True`]: crate::types::True +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct EditInlineMessageCaption { + #[serde(skip_serializing)] + bot: Bot, + inline_message_id: String, + caption: Option, + parse_mode: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for EditInlineMessageCaption { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "editMessageCaption", &self).await + } +} + +impl EditInlineMessageCaption { + pub(crate) fn new(bot: Bot, inline_message_id: I) -> Self + where + I: Into, + { + let inline_message_id = inline_message_id.into(); + Self { bot, inline_message_id, caption: None, parse_mode: None, reply_markup: None } + } + + /// Identifier of the inline message. + pub fn inline_message_id(mut self, val: T) -> Self + where + T: Into, + { + self.inline_message_id = val.into(); + self + } + + /// New caption of the message. + pub fn caption(mut self, val: T) -> Self + where + T: Into, + { + self.caption = Some(val.into()); + self + } + + /// Send [Markdown] or [HTML], if you want Telegram apps to show + /// [bold, italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: crate::types::ParseMode::Markdown + /// [HTML]: crate::types::ParseMode::HTML + /// [bold, italic, fixed-width text or inline URLs]: + /// crate::types::ParseMode + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + /// A JSON-serialized object for an [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/edit_inline_message_live_location.rs b/src/requests/all/edit_inline_message_live_location.rs new file mode 100644 index 00000000..4b267427 --- /dev/null +++ b/src/requests/all/edit_inline_message_live_location.rs @@ -0,0 +1,77 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{InlineKeyboardMarkup, True}, + Bot, +}; + +/// Use this method to edit live location messages sent via the bot. +/// +/// A location can be edited until its live_period expires or editing is +/// explicitly disabled by a call to stopMessageLiveLocation. On success, +/// [`True`] is returned. +/// +/// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation). +/// +/// [`True`]: crate::types::True +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct EditInlineMessageLiveLocation { + #[serde(skip_serializing)] + bot: Bot, + inline_message_id: String, + latitude: f32, + longitude: f32, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for EditInlineMessageLiveLocation { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "editMessageLiveLocation", &self) + .await + } +} + +impl EditInlineMessageLiveLocation { + pub(crate) fn new(bot: Bot, inline_message_id: I, latitude: f32, longitude: f32) -> Self + where + I: Into, + { + let inline_message_id = inline_message_id.into(); + Self { bot, inline_message_id, latitude, longitude, reply_markup: None } + } + + /// Identifier of the inline message. + pub fn inline_message_id(mut self, val: T) -> Self + where + T: Into, + { + self.inline_message_id = val.into(); + self + } + + /// Latitude of new location. + pub fn latitude(mut self, val: f32) -> Self { + self.latitude = val; + self + } + + /// Longitude of new location. + pub fn longitude(mut self, val: f32) -> Self { + self.longitude = val; + self + } + + /// A JSON-serialized object for a new [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/edit_inline_message_media.rs b/src/requests/all/edit_inline_message_media.rs new file mode 100644 index 00000000..9a11f126 --- /dev/null +++ b/src/requests/all/edit_inline_message_media.rs @@ -0,0 +1,78 @@ +use crate::{ + net, + requests::{form_builder::FormBuilder, Request, ResponseResult}, + types::{InlineKeyboardMarkup, InputMedia, True}, + Bot, +}; + +/// Use this method to edit animation, audio, document, photo, or video +/// messages sent via the bot. +/// +/// If a message is a part of a message album, then it can be edited only to a +/// photo or a video. Otherwise, message type can be changed arbitrarily. When +/// this method is used, new file can't be uploaded. Use previously +/// uploaded file via its `file_id` or specify a URL. On success, [`True`] is +/// returned. +/// +/// [The official docs](https://core.telegram.org/bots/api#editmessagemedia). +/// +/// [`True`]: crate::types::True +#[derive(Debug, Clone)] +pub struct EditInlineMessageMedia { + bot: Bot, + inline_message_id: String, + media: InputMedia, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for EditInlineMessageMedia { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_multipart( + self.bot.client(), + self.bot.token(), + "editMessageMedia", + FormBuilder::new() + .add_text("media", &self.media) + .add_text("reply_markup", &self.reply_markup) + .add_text("inline_message_id", &self.inline_message_id) + .build(), + ) + .await + } +} + +impl EditInlineMessageMedia { + pub(crate) fn new(bot: Bot, inline_message_id: I, media: InputMedia) -> Self + where + I: Into, + { + let inline_message_id = inline_message_id.into(); + Self { bot, inline_message_id, media, reply_markup: None } + } + + /// Identifier of the inline message. + pub fn inline_message_id(mut self, val: T) -> Self + where + T: Into, + { + self.inline_message_id = val.into(); + self + } + + /// A JSON-serialized object for a new media content of the message. + pub fn media(mut self, val: InputMedia) -> Self { + self.media = val; + self + } + + /// A JSON-serialized object for a new [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/edit_inline_message_reply_markup.rs b/src/requests/all/edit_inline_message_reply_markup.rs new file mode 100644 index 00000000..cd96c686 --- /dev/null +++ b/src/requests/all/edit_inline_message_reply_markup.rs @@ -0,0 +1,62 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{InlineKeyboardMarkup, True}, + Bot, +}; + +/// Use this method to edit only the reply markup of messages sent via the bot. +/// +/// On success, [`True`] is returned. +/// +/// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup). +/// +/// [`Message`]: crate::types::Message +/// [`True`]: crate::types::True +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct EditInlineMessageReplyMarkup { + #[serde(skip_serializing)] + bot: Bot, + inline_message_id: String, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for EditInlineMessageReplyMarkup { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "editMessageReplyMarkup", &self) + .await + } +} + +impl EditInlineMessageReplyMarkup { + pub(crate) fn new(bot: Bot, inline_message_id: I) -> Self + where + I: Into, + { + let inline_message_id = inline_message_id.into(); + Self { bot, inline_message_id, reply_markup: None } + } + + /// Identifier of the inline message. + pub fn inline_message_id(mut self, val: T) -> Self + where + T: Into, + { + self.inline_message_id = val.into(); + self + } + + /// A JSON-serialized object for an [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/edit_inline_message_text.rs b/src/requests/all/edit_inline_message_text.rs new file mode 100644 index 00000000..2b266314 --- /dev/null +++ b/src/requests/all/edit_inline_message_text.rs @@ -0,0 +1,98 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{InlineKeyboardMarkup, Message, ParseMode}, + Bot, +}; + +/// Use this method to edit text and game messages sent via the bot. +/// +/// On success, [`True`] is returned. +/// +/// [The official docs](https://core.telegram.org/bots/api#editmessagetext). +/// +/// [`True`]: crate::types::True +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct EditInlineMessageText { + #[serde(skip_serializing)] + bot: Bot, + inline_message_id: String, + text: String, + parse_mode: Option, + disable_web_page_preview: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for EditInlineMessageText { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "editMessageText", &self).await + } +} + +impl EditInlineMessageText { + pub(crate) fn new(bot: Bot, inline_message_id: I, text: T) -> Self + where + I: Into, + T: Into, + { + let inline_message_id = inline_message_id.into(); + let text = text.into(); + Self { + bot, + inline_message_id, + text, + parse_mode: None, + disable_web_page_preview: None, + reply_markup: None, + } + } + + /// Identifier of the inline message. + pub fn inline_message_id(mut self, val: T) -> Self + where + T: Into, + { + self.inline_message_id = val.into(); + self + } + + /// New text of the message. + pub fn text(mut self, val: T) -> Self + where + T: Into, + { + self.text = val.into(); + self + } + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in your bot's message. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + /// Disables link previews for links in this message. + pub fn disable_web_page_preview(mut self, val: bool) -> Self { + self.disable_web_page_preview = Some(val); + self + } + + /// A JSON-serialized object for an [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/edit_message_caption.rs b/src/requests/all/edit_message_caption.rs new file mode 100644 index 00000000..9294af93 --- /dev/null +++ b/src/requests/all/edit_message_caption.rs @@ -0,0 +1,91 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, InlineKeyboardMarkup, Message, ParseMode}, + Bot, +}; + +/// Use this method to edit captions of messages sent by the bot. +/// +/// On success, the edited [`Message`] is returned. +/// +/// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). +/// +/// [`Message`]: crate::types::Message +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct EditMessageCaption { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + message_id: i32, + caption: Option, + parse_mode: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for EditMessageCaption { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "editMessageCaption", &self).await + } +} + +impl EditMessageCaption { + pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id, message_id, caption: None, parse_mode: None, reply_markup: None } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`) + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Identifier of the message to edit + pub fn message_id(mut self, val: i32) -> Self { + self.message_id = val; + self + } + + /// New caption of the message. + pub fn caption(mut self, val: T) -> Self + where + T: Into, + { + self.caption = Some(val.into()); + self + } + + /// Send [Markdown] or [HTML], if you want Telegram apps to show + /// [bold, italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: crate::types::ParseMode::Markdown + /// [HTML]: crate::types::ParseMode::HTML + /// [bold, italic, fixed-width text or inline URLs]: + /// crate::types::ParseMode + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + /// A JSON-serialized object for an [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/edit_message_live_location.rs b/src/requests/all/edit_message_live_location.rs new file mode 100644 index 00000000..c5afd295 --- /dev/null +++ b/src/requests/all/edit_message_live_location.rs @@ -0,0 +1,91 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, InlineKeyboardMarkup, Message}, + Bot, +}; + +/// Use this method to edit live location messages. +/// +/// A location can be edited until its live_period expires or editing is +/// explicitly disabled by a call to stopMessageLiveLocation. On success, the +/// edited [`Message`] is returned. +/// +/// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation). +/// +/// [`Message`]: crate::types::Message +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct EditMessageLiveLocation { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + message_id: i32, + latitude: f32, + longitude: f32, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for EditMessageLiveLocation { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "editMessageLiveLocation", &self) + .await + } +} + +impl EditMessageLiveLocation { + pub(crate) fn new( + bot: Bot, + chat_id: C, + message_id: i32, + latitude: f32, + longitude: f32, + ) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id, message_id, latitude, longitude, reply_markup: None } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`) + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Identifier of the message to edit + pub fn message_id(mut self, val: i32) -> Self { + self.message_id = val; + self + } + + /// Latitude of new location. + pub fn latitude(mut self, val: f32) -> Self { + self.latitude = val; + self + } + + /// Longitude of new location. + pub fn longitude(mut self, val: f32) -> Self { + self.longitude = val; + self + } + + /// A JSON-serialized object for a new [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/edit_message_media.rs b/src/requests/all/edit_message_media.rs new file mode 100644 index 00000000..7f15ed5f --- /dev/null +++ b/src/requests/all/edit_message_media.rs @@ -0,0 +1,85 @@ +use crate::{ + net, + requests::{form_builder::FormBuilder, Request, ResponseResult}, + types::{ChatId, InlineKeyboardMarkup, InputMedia, Message}, + Bot, +}; + +/// Use this method to edit animation, audio, document, photo, or video +/// messages. +/// +/// If a message is a part of a message album, then it can be edited only to a +/// photo or a video. Otherwise, message type can be changed arbitrarily. On +/// success, the edited [`Message`] is returned. +/// +/// [The official docs](https://core.telegram.org/bots/api#editmessagemedia). +/// +/// [`Message`]: crate::types::Message +#[derive(Debug, Clone)] +pub struct EditMessageMedia { + bot: Bot, + chat_id: ChatId, + message_id: i32, + media: InputMedia, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for EditMessageMedia { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_multipart( + self.bot.client(), + self.bot.token(), + "editMessageMedia", + FormBuilder::new() + .add_text("chat_id", &self.chat_id) + .add_text("message_id", &self.message_id) + .add_text("media", &self.media) + .add_text("reply_markup", &self.reply_markup) + .build(), + ) + .await + } +} + +impl EditMessageMedia { + pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32, media: InputMedia) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id, message_id, media, reply_markup: None } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`) + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Identifier of the message to edit + pub fn message_id(mut self, val: i32) -> Self { + self.message_id = val; + self + } + + /// A JSON-serialized object for a new media content of the message. + pub fn media(mut self, val: InputMedia) -> Self { + self.media = val; + self + } + + /// A JSON-serialized object for a new [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/edit_message_reply_markup.rs b/src/requests/all/edit_message_reply_markup.rs new file mode 100644 index 00000000..66aab5bf --- /dev/null +++ b/src/requests/all/edit_message_reply_markup.rs @@ -0,0 +1,69 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, InlineKeyboardMarkup, Message}, + Bot, +}; + +/// Use this method to edit only the reply markup of messages. +/// +/// On success, the edited [`Message`] is returned. +/// +/// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup). +/// +/// [`Message`]: crate::types::Message +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct EditMessageReplyMarkup { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + message_id: i32, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for EditMessageReplyMarkup { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "editMessageReplyMarkup", &self) + .await + } +} + +impl EditMessageReplyMarkup { + pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id, message_id, reply_markup: None } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`) + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Identifier of the message to edit + pub fn message_id(mut self, val: i32) -> Self { + self.message_id = val; + self + } + + /// A JSON-serialized object for an [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/edit_message_text.rs b/src/requests/all/edit_message_text.rs new file mode 100644 index 00000000..12251313 --- /dev/null +++ b/src/requests/all/edit_message_text.rs @@ -0,0 +1,107 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, InlineKeyboardMarkup, Message, ParseMode}, + Bot, +}; + +/// Use this method to edit text and game messages. +/// +/// On success, the edited [`Message`] is returned. +/// +/// [The official docs](https://core.telegram.org/bots/api#editmessagetext). +/// +/// [`Message`]: crate::types::Message +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct EditMessageText { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + message_id: i32, + text: String, + parse_mode: Option, + disable_web_page_preview: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for EditMessageText { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "editMessageText", &self).await + } +} + +impl EditMessageText { + pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32, text: T) -> Self + where + C: Into, + T: Into, + { + let chat_id = chat_id.into(); + let text = text.into(); + Self { + bot, + chat_id, + message_id, + text, + parse_mode: None, + disable_web_page_preview: None, + reply_markup: None, + } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`) + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Identifier of the message to edit + pub fn message_id(mut self, val: i32) -> Self { + self.message_id = val; + self + } + + /// New text of the message. + pub fn text(mut self, val: T) -> Self + where + T: Into, + { + self.text = val.into(); + self + } + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in your bot's message. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + /// Disables link previews for links in this message. + pub fn disable_web_page_preview(mut self, val: bool) -> Self { + self.disable_web_page_preview = Some(val); + self + } + + /// A JSON-serialized object for an [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/export_chat_invite_link.rs b/src/requests/all/export_chat_invite_link.rs new file mode 100644 index 00000000..51a26437 --- /dev/null +++ b/src/requests/all/export_chat_invite_link.rs @@ -0,0 +1,65 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::ChatId, + Bot, +}; + +/// Use this method to generate a new invite link for a chat; any previously +/// generated link is revoked. +/// +/// The bot must be an administrator in the chat for this to work and must have +/// the appropriate admin rights. +/// +/// ## Note +/// Each administrator in a chat generates their own invite links. Bots can't +/// use invite links generated by other administrators. If you want your bot to +/// work with invite links, it will need to generate its own link using +/// [`Bot::export_chat_invite_link`] – after this the link will become available +/// to the bot via the [`Bot::get_chat`] method. If your bot needs to generate a +/// new invite link replacing its previous one, use +/// [`Bot::export_chat_invite_link`] again. +/// +/// [The official docs](https://core.telegram.org/bots/api#exportchatinvitelink). +/// +/// [`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(Debug, Clone, Serialize)] +pub struct ExportChatInviteLink { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, +} + +#[async_trait::async_trait] +impl Request for ExportChatInviteLink { + type Output = String; + + /// Returns the new invite link as `String` on success. + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "exportChatInviteLink", &self).await + } +} + +impl ExportChatInviteLink { + pub(crate) fn new(bot: Bot, chat_id: C) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } +} diff --git a/src/requests/all/forward_message.rs b/src/requests/all/forward_message.rs new file mode 100644 index 00000000..feb9ec02 --- /dev/null +++ b/src/requests/all/forward_message.rs @@ -0,0 +1,81 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, Message}, + Bot, +}; + +/// 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(Debug, Clone, Serialize)] +pub struct ForwardMessage { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + from_chat_id: ChatId, + disable_notification: Option, + message_id: i32, +} + +#[async_trait::async_trait] +impl Request for ForwardMessage { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "forwardMessage", &self).await + } +} + +impl ForwardMessage { + pub(crate) fn new(bot: Bot, chat_id: C, from_chat_id: F, message_id: i32) -> Self + where + C: Into, + F: Into, + { + let chat_id = chat_id.into(); + let from_chat_id = from_chat_id.into(); + Self { bot, chat_id, from_chat_id, message_id, disable_notification: None } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Unique identifier for the chat where the original message was sent (or + /// channel username in the format `@channelusername`). + #[allow(clippy::wrong_self_convention)] + pub fn from_chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.from_chat_id = val.into(); + self + } + + /// Sends the message [silently]. Users will receive a notification with no + /// sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_notification(mut self, val: bool) -> Self { + self.disable_notification = Some(val); + self + } + + /// Message identifier in the chat specified in [`from_chat_id`]. + /// + /// [`from_chat_id`]: ForwardMessage::from_chat_id + pub fn message_id(mut self, val: i32) -> Self { + self.message_id = val; + self + } +} diff --git a/src/requests/all/get_chat.rs b/src/requests/all/get_chat.rs new file mode 100644 index 00000000..11406776 --- /dev/null +++ b/src/requests/all/get_chat.rs @@ -0,0 +1,50 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{Chat, ChatId}, + Bot, +}; + +/// 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 +/// or channel, etc.). +/// +/// [The official docs](https://core.telegram.org/bots/api#getchat). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct GetChat { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, +} + +#[async_trait::async_trait] +impl Request for GetChat { + type Output = Chat; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "getChat", &self).await + } +} + +impl GetChat { + pub(crate) fn new(bot: Bot, chat_id: C) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id } + } + + /// Unique identifier for the target chat or username of the target + /// supergroup or channel (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } +} diff --git a/src/requests/all/get_chat_administrators.rs b/src/requests/all/get_chat_administrators.rs new file mode 100644 index 00000000..06657ec5 --- /dev/null +++ b/src/requests/all/get_chat_administrators.rs @@ -0,0 +1,53 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, ChatMember}, + Bot, +}; + +/// Use this method to get a list of administrators in a chat. +/// +/// If the chat is a group or a supergroup and no administrators were appointed, +/// only the creator will be returned. +/// +/// [The official docs](https://core.telegram.org/bots/api#getchatadministrators). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct GetChatAdministrators { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, +} + +#[async_trait::async_trait] +impl Request for GetChatAdministrators { + type Output = Vec; + + /// On success, returns an array that contains information about all chat + /// administrators except other bots. + async fn send(&self) -> ResponseResult> { + net::request_json(self.bot.client(), self.bot.token(), "getChatAdministrators", &self).await + } +} + +impl GetChatAdministrators { + pub(crate) fn new(bot: Bot, chat_id: C) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id } + } + + /// Unique identifier for the target chat or username of the target + /// supergroup or channel (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } +} diff --git a/src/requests/all/get_chat_member.rs b/src/requests/all/get_chat_member.rs new file mode 100644 index 00000000..4ebe3f56 --- /dev/null +++ b/src/requests/all/get_chat_member.rs @@ -0,0 +1,55 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, ChatMember}, + Bot, +}; + +/// 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(Debug, Clone, Serialize)] +pub struct GetChatMember { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + user_id: i32, +} + +#[async_trait::async_trait] +impl Request for GetChatMember { + type Output = ChatMember; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "getChatMember", &self).await + } +} + +impl GetChatMember { + pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id, user_id } + } + + /// Unique identifier for the target chat or username of the target + /// supergroup or channel (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Unique identifier of the target user. + pub fn user_id(mut self, val: i32) -> Self { + self.user_id = val; + self + } +} diff --git a/src/requests/all/get_chat_members_count.rs b/src/requests/all/get_chat_members_count.rs new file mode 100644 index 00000000..5c78596b --- /dev/null +++ b/src/requests/all/get_chat_members_count.rs @@ -0,0 +1,48 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::ChatId, + Bot, +}; + +/// 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(Debug, Clone, Serialize)] +pub struct GetChatMembersCount { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, +} + +#[async_trait::async_trait] +impl Request for GetChatMembersCount { + type Output = i32; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "getChatMembersCount", &self).await + } +} + +impl GetChatMembersCount { + pub(crate) fn new(bot: Bot, chat_id: C) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id } + } + + /// Unique identifier for the target chat or username of the target + /// supergroup or channel (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } +} diff --git a/src/requests/all/get_file.rs b/src/requests/all/get_file.rs new file mode 100644 index 00000000..d2d0556c --- /dev/null +++ b/src/requests/all/get_file.rs @@ -0,0 +1,62 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::File, + Bot, +}; + +/// Use this method to get basic info about a file and prepare it for +/// downloading. +/// +/// For the moment, bots can download files of up to `20MB` in size. +/// +/// The file can then be downloaded via the link +/// `https://api.telegram.org/file/bot/`, where `` +/// is taken from the response. 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 [`GetFile`] again. +/// +/// **Note**: This function may not preserve the original file name and MIME +/// type. You should save the file's MIME type and name (if available) when the +/// [`File`] object is received. +/// +/// [The official docs](https://core.telegram.org/bots/api#getfile). +/// +/// [`File`]: crate::types::File +/// [`GetFile`]: self::GetFile +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct GetFile { + #[serde(skip_serializing)] + bot: Bot, + file_id: String, +} + +#[async_trait::async_trait] +impl Request for GetFile { + type Output = File; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "getFile", &self).await + } +} + +impl GetFile { + pub(crate) fn new(bot: Bot, file_id: F) -> Self + where + F: Into, + { + Self { bot, file_id: file_id.into() } + } + + /// File identifier to get info about. + pub fn file_id(mut self, value: F) -> Self + where + F: Into, + { + self.file_id = value.into(); + self + } +} diff --git a/src/requests/all/get_game_high_scores.rs b/src/requests/all/get_game_high_scores.rs new file mode 100644 index 00000000..1a43d63e --- /dev/null +++ b/src/requests/all/get_game_high_scores.rs @@ -0,0 +1,63 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{GameHighScore, TargetMessage}, + Bot, +}; + +/// Use this method to get data for high score tables. +/// +/// Will return the score of the specified user and several of his neighbors in +/// a game. +/// +/// ## Note +/// This method will currently return scores for the target user, plus two of +/// his closest neighbors on each side. Will also return the top three users if +/// the user and his neighbors are not among them. Please note that this +/// behavior is subject to change. +/// +/// [The official docs](https://core.telegram.org/bots/api#getgamehighscores) +#[derive(Debug, Clone, Serialize)] +pub struct GetGameHighScores { + #[serde(skip_serializing)] + bot: Bot, + #[serde(flatten)] + target: TargetMessage, + user_id: i32, +} + +#[async_trait::async_trait] +impl Request for GetGameHighScores { + type Output = Vec; + + async fn send(&self) -> ResponseResult> { + net::request_json(self.bot.client(), self.bot.token(), "getGameHighScores", &self).await + } +} + +impl GetGameHighScores { + pub(crate) fn new(bot: Bot, target: T, user_id: i32) -> Self + where + T: Into, + { + let target = target.into(); + Self { bot, target, user_id } + } + + /// Target message, either chat id and message id or inline message id. + pub fn target(mut self, val: T) -> Self + where + T: Into, + { + self.target = val.into(); + self + } + + /// Target user id. + pub fn user_id(mut self, val: i32) -> Self { + self.user_id = val; + self + } +} diff --git a/src/requests/all/get_me.rs b/src/requests/all/get_me.rs new file mode 100644 index 00000000..567b4375 --- /dev/null +++ b/src/requests/all/get_me.rs @@ -0,0 +1,33 @@ +use crate::{ + net, + requests::{Request, ResponseResult}, + types::Me, + Bot, +}; +use serde::Serialize; + +/// A simple method for testing your bot's auth token. Requires no parameters. +/// +/// [The official docs](https://core.telegram.org/bots/api#getme). +#[derive(Debug, Clone, Serialize)] +pub struct GetMe { + #[serde(skip_serializing)] + bot: Bot, +} + +#[async_trait::async_trait] +impl Request for GetMe { + type Output = Me; + + /// Returns basic information about the bot. + #[allow(clippy::trivially_copy_pass_by_ref)] + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "getMe", &self).await + } +} + +impl GetMe { + pub(crate) fn new(bot: Bot) -> Self { + Self { bot } + } +} diff --git a/src/requests/all/get_my_commands.rs b/src/requests/all/get_my_commands.rs new file mode 100644 index 00000000..586bfa28 --- /dev/null +++ b/src/requests/all/get_my_commands.rs @@ -0,0 +1,33 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::BotCommand, + Bot, +}; + +/// Use this method to get the current list of the bot's commands. +/// +/// [The official docs](https://core.telegram.org/bots/api#getmycommands). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct GetMyCommands { + #[serde(skip_serializing)] + bot: Bot, +} + +#[async_trait::async_trait] +impl Request for GetMyCommands { + type Output = Vec; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "getMyCommands", &self).await + } +} + +impl GetMyCommands { + pub(crate) fn new(bot: Bot) -> Self { + Self { bot } + } +} diff --git a/src/requests/all/get_sticker_set.rs b/src/requests/all/get_sticker_set.rs new file mode 100644 index 00000000..a8a83bea --- /dev/null +++ b/src/requests/all/get_sticker_set.rs @@ -0,0 +1,47 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::StickerSet, + Bot, +}; + +/// 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(Debug, Clone, Serialize)] +pub struct GetStickerSet { + #[serde(skip_serializing)] + bot: Bot, + name: String, +} + +#[async_trait::async_trait] +impl Request for GetStickerSet { + type Output = StickerSet; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "getStickerSet", &self).await + } +} + +impl GetStickerSet { + pub(crate) fn new(bot: Bot, name: N) -> Self + where + N: Into, + { + let name = name.into(); + Self { bot, name } + } + + /// Name of the sticker set. + pub fn name(mut self, val: T) -> Self + where + T: Into, + { + self.name = val.into(); + self + } +} diff --git a/src/requests/all/get_updates.rs b/src/requests/all/get_updates.rs new file mode 100644 index 00000000..f6fe2d2b --- /dev/null +++ b/src/requests/all/get_updates.rs @@ -0,0 +1,108 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{AllowedUpdate, Update}, + Bot, +}; + +/// Use this method to receive incoming updates using long polling ([wiki]). +/// +/// **Notes:** +/// 1. This method will not work if an outgoing webhook is set up. +/// 2. In order to avoid getting duplicate updates, +/// recalculate offset after each server response. +/// +/// [The official docs](https://core.telegram.org/bots/api#getupdates). +/// +/// [wiki]: https://en.wikipedia.org/wiki/Push_technology#Long_polling +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct GetUpdates { + #[serde(skip_serializing)] + bot: Bot, + pub(crate) offset: Option, + pub(crate) limit: Option, + pub(crate) timeout: Option, + pub(crate) allowed_updates: Option>, +} + +// TODO: Add version of this method that will ignore unparsed updates, +// to be bullet proof +#[async_trait::async_trait] +impl Request for GetUpdates { + type Output = Vec; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "getUpdates", &self).await + } +} + +impl GetUpdates { + pub(crate) fn new(bot: Bot) -> Self { + Self { bot, offset: None, limit: None, timeout: None, allowed_updates: None } + } + + /// Identifier of the first update to be returned. + /// + /// Must be greater by one than the highest among the identifiers of + /// previously received updates. By default, updates starting with the + /// earliest unconfirmed update are returned. An update is considered + /// confirmed as soon as [`GetUpdates`] is called with an [`offset`] + /// higher than its [`id`]. The negative offset can be specified to + /// retrieve updates starting from `-offset` update from the end of the + /// updates queue. All previous updates will forgotten. + /// + /// [`GetUpdates`]: self::GetUpdates + /// [`offset`]: self::GetUpdates::offset + /// [`id`]: crate::types::Update::id + pub fn offset(mut self, value: i32) -> Self { + self.offset = Some(value); + self + } + + /// Limits the number of updates to be retrieved. + /// + /// Values between `1`—`100` are accepted. Defaults to `100`. + pub fn limit(mut self, value: u8) -> Self { + self.limit = Some(value); + self + } + + /// Timeout in seconds for long polling. + /// + /// Defaults to `0`, i.e. usual short polling. Should be positive, short + /// polling should be used for testing purposes only. + pub fn timeout(mut self, value: u32) -> Self { + self.timeout = Some(value); + self + } + + /// List the types of updates you want your bot to receive. + /// + /// For example, specify [[`Message`], [`EditedChannelPost`], + /// [`CallbackQuery`]] to only receive updates of these types. + /// See [`AllowedUpdate`] for a complete list of available update types. + /// + /// Specify an empty list to receive all updates regardless of type + /// (default). If not specified, the previous setting will be used. + /// + /// **Note:** + /// This parameter doesn't affect updates created before the call to the + /// [`Bot::get_updates`], so unwanted updates may be received for a short + /// period of time. + /// + /// [`Message`]: self::AllowedUpdate::Message + /// [`EditedChannelPost`]: self::AllowedUpdate::EditedChannelPost + /// [`CallbackQuery`]: self::AllowedUpdate::CallbackQuery + /// [`AllowedUpdate`]: self::AllowedUpdate + /// [`Bot::get_updates`]: crate::Bot::get_updates + pub fn allowed_updates(mut self, value: T) -> Self + where + T: Into>, + { + self.allowed_updates = Some(value.into()); + self + } +} diff --git a/src/requests/all/get_user_profile_photos.rs b/src/requests/all/get_user_profile_photos.rs new file mode 100644 index 00000000..663a07a4 --- /dev/null +++ b/src/requests/all/get_user_profile_photos.rs @@ -0,0 +1,58 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::UserProfilePhotos, + Bot, +}; + +/// 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(Debug, Clone, Serialize)] +pub struct GetUserProfilePhotos { + #[serde(skip_serializing)] + bot: Bot, + user_id: i32, + offset: Option, + limit: Option, +} + +#[async_trait::async_trait] +impl Request for GetUserProfilePhotos { + type Output = UserProfilePhotos; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "getUserProfilePhotos", &self).await + } +} + +impl GetUserProfilePhotos { + pub(crate) fn new(bot: Bot, user_id: i32) -> Self { + Self { bot, user_id, offset: None, limit: None } + } + + /// Unique identifier of the target user. + pub fn user_id(mut self, val: i32) -> Self { + self.user_id = val; + self + } + + /// Sequential number of the first photo to be returned. By default, all + /// photos are returned. + pub fn offset(mut self, val: i32) -> Self { + self.offset = Some(val); + self + } + + /// Limits the number of photos to be retrieved. Values between 1—100 are + /// accepted. + /// + /// Defaults to 100. + pub fn limit(mut self, val: i32) -> Self { + self.limit = Some(val); + self + } +} diff --git a/src/requests/all/get_webhook_info.rs b/src/requests/all/get_webhook_info.rs new file mode 100644 index 00000000..51424471 --- /dev/null +++ b/src/requests/all/get_webhook_info.rs @@ -0,0 +1,38 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::WebhookInfo, + Bot, +}; + +/// Use this method to get current webhook status. +/// +/// If the bot is using [`Bot::get_updates`], will return an object with the url +/// field empty. +/// +/// [The official docs](https://core.telegram.org/bots/api#getwebhookinfo). +/// +/// [`Bot::get_updates`]: crate::Bot::get_updates +#[derive(Debug, Clone, Serialize)] +pub struct GetWebhookInfo { + #[serde(skip_serializing)] + bot: Bot, +} + +#[async_trait::async_trait] +impl Request for GetWebhookInfo { + type Output = WebhookInfo; + + #[allow(clippy::trivially_copy_pass_by_ref)] + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "getWebhookInfo", &self).await + } +} + +impl GetWebhookInfo { + pub(crate) fn new(bot: Bot) -> Self { + Self { bot } + } +} diff --git a/src/requests/all/kick_chat_member.rs b/src/requests/all/kick_chat_member.rs new file mode 100644 index 00000000..31953949 --- /dev/null +++ b/src/requests/all/kick_chat_member.rs @@ -0,0 +1,72 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, True}, + Bot, +}; + +/// Use this method to kick a user from a group, a supergroup or a channel. +/// +/// In the case of supergroups and channels, the user will not be able to return +/// to the group on their own using invite links, etc., unless [unbanned] first. +/// The bot must be an administrator in the chat for this to work and must have +/// the appropriate admin rights. +/// +/// [The official docs](https://core.telegram.org/bots/api#kickchatmember). +/// +/// [unbanned]: crate::Bot::unban_chat_member +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct KickChatMember { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + user_id: i32, + until_date: Option, +} + +#[async_trait::async_trait] +impl Request for KickChatMember { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "kickChatMember", &self).await + } +} + +impl KickChatMember { + pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id, user_id, until_date: None } + } + + /// Unique identifier for the target group or username of the target + /// supergroup or channel (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Unique identifier of the target user. + pub fn user_id(mut self, val: i32) -> Self { + self.user_id = val; + self + } + + /// Date when the user will be unbanned, unix time. + /// + /// If user is banned for more than 366 days or less than 30 seconds from + /// the current time they are considered to be banned forever. + pub fn until_date(mut self, val: i32) -> Self { + self.until_date = Some(val); + self + } +} diff --git a/src/requests/all/leave_chat.rs b/src/requests/all/leave_chat.rs new file mode 100644 index 00000000..d0411efb --- /dev/null +++ b/src/requests/all/leave_chat.rs @@ -0,0 +1,48 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, True}, + Bot, +}; + +/// 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(Debug, Clone, Serialize)] +pub struct LeaveChat { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, +} + +#[async_trait::async_trait] +impl Request for LeaveChat { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "leaveChat", &self).await + } +} + +impl LeaveChat { + pub(crate) fn new(bot: Bot, chat_id: C) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id } + } + + /// Unique identifier for the target chat or username of the target + /// supergroup or channel (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } +} diff --git a/src/requests/all/mod.rs b/src/requests/all/mod.rs new file mode 100644 index 00000000..3de4d87a --- /dev/null +++ b/src/requests/all/mod.rs @@ -0,0 +1,152 @@ +mod add_sticker_to_set; +mod answer_callback_query; +mod answer_inline_query; +mod answer_pre_checkout_query; +mod answer_shipping_query; +mod create_new_sticker_set; +mod delete_chat_photo; +mod delete_chat_sticker_set; +mod delete_message; +mod delete_sticker_from_set; +mod delete_webhook; +mod edit_inline_message_caption; +mod edit_inline_message_live_location; +mod edit_inline_message_media; +mod edit_inline_message_reply_markup; +mod edit_inline_message_text; +mod edit_message_caption; +mod edit_message_live_location; +mod edit_message_media; +mod edit_message_reply_markup; +mod edit_message_text; +mod export_chat_invite_link; +mod forward_message; +mod get_chat; +mod get_chat_administrators; +mod get_chat_member; +mod get_chat_members_count; +mod get_file; +mod get_game_high_scores; +mod get_me; +mod get_my_commands; +mod get_sticker_set; +mod get_updates; +mod get_user_profile_photos; +mod get_webhook_info; +mod kick_chat_member; +mod leave_chat; +mod pin_chat_message; +mod promote_chat_member; +mod restrict_chat_member; +mod send_animation; +mod send_audio; +mod send_chat_action; +mod send_contact; +mod send_dice; +mod send_document; +mod send_game; +mod send_invoice; +mod send_location; +mod send_media_group; +mod send_message; +mod send_photo; +mod send_poll; +mod send_sticker; +mod send_venue; +mod send_video; +mod send_video_note; +mod send_voice; +mod set_chat_administrator_custom_title; +mod set_chat_description; +mod set_chat_permissions; +mod set_chat_photo; +mod set_chat_sticker_set; +mod set_chat_title; +mod set_game_score; +mod set_my_commands; +mod set_sticker_position_in_set; +mod set_sticker_set_thumb; +mod set_webhook; +mod stop_inline_message_live_location; +mod stop_message_live_location; +mod stop_poll; +mod unban_chat_member; +mod unpin_chat_message; +mod upload_sticker_file; + +pub use add_sticker_to_set::*; +pub use answer_callback_query::*; +pub use answer_inline_query::*; +pub use answer_pre_checkout_query::*; +pub use answer_shipping_query::*; +pub use create_new_sticker_set::*; +pub use delete_chat_photo::*; +pub use delete_chat_sticker_set::*; +pub use delete_message::*; +pub use delete_sticker_from_set::*; +pub use delete_webhook::*; +pub use edit_inline_message_caption::*; +pub use edit_inline_message_live_location::*; +pub use edit_inline_message_media::*; +pub use edit_inline_message_reply_markup::*; +pub use edit_inline_message_text::*; +pub use edit_message_caption::*; +pub use edit_message_live_location::*; +pub use edit_message_media::*; +pub use edit_message_reply_markup::*; +pub use edit_message_text::*; +pub use export_chat_invite_link::*; +pub use forward_message::*; +pub use get_chat::*; +pub use get_chat_administrators::*; +pub use get_chat_member::*; +pub use get_chat_members_count::*; +pub use get_file::*; +pub use get_game_high_scores::*; +pub use get_me::*; +pub use get_my_commands::*; +pub use get_sticker_set::*; +pub use get_updates::*; +pub use get_user_profile_photos::*; +pub use get_webhook_info::*; +pub use kick_chat_member::*; +pub use leave_chat::*; +pub use pin_chat_message::*; +pub use promote_chat_member::*; +pub use restrict_chat_member::*; +pub use send_animation::*; +pub use send_audio::*; +pub use send_chat_action::*; +pub use send_contact::*; +pub use send_dice::*; +pub use send_document::*; +pub use send_game::*; +pub use send_invoice::*; +pub use send_location::*; +pub use send_media_group::*; +pub use send_message::*; +pub use send_photo::*; +pub use send_poll::*; +pub use send_sticker::*; +pub use send_venue::*; +pub use send_video::*; +pub use send_video_note::*; +pub use send_voice::*; +pub use set_chat_administrator_custom_title::*; +pub use set_chat_description::*; +pub use set_chat_permissions::*; +pub use set_chat_photo::*; +pub use set_chat_sticker_set::*; +pub use set_chat_title::*; +pub use set_game_score::*; +pub use set_my_commands::*; +pub use set_sticker_position_in_set::*; +pub use set_sticker_set_thumb::*; +pub use set_webhook::*; +pub use std::pin::Pin; +pub use stop_inline_message_live_location::*; +pub use stop_message_live_location::*; +pub use stop_poll::*; +pub use unban_chat_member::*; +pub use unpin_chat_message::*; +pub use upload_sticker_file::*; diff --git a/src/requests/all/pin_chat_message.rs b/src/requests/all/pin_chat_message.rs new file mode 100644 index 00000000..ae1f3227 --- /dev/null +++ b/src/requests/all/pin_chat_message.rs @@ -0,0 +1,69 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, True}, + Bot, +}; + +/// Use this method to pin a message in a group, a supergroup, or a channel. +/// +/// The bot must be an administrator in the chat for this to work and must have +/// the `can_pin_messages` admin right in the supergroup or `can_edit_messages` +/// admin right in the channel. +/// +/// [The official docs](https://core.telegram.org/bots/api#pinchatmessage). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct PinChatMessage { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + message_id: i32, + disable_notification: Option, +} + +#[async_trait::async_trait] +impl Request for PinChatMessage { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "pinChatMessage", &self).await + } +} + +impl PinChatMessage { + pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id, message_id, disable_notification: None } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Identifier of a message to pin. + pub fn message_id(mut self, val: i32) -> Self { + self.message_id = val; + self + } + + /// Pass `true`, if it is not necessary to send a notification to all chat + /// members about the new pinned message. + /// + /// Notifications are always disabled in channels. + pub fn disable_notification(mut self, val: bool) -> Self { + self.disable_notification = Some(val); + self + } +} diff --git a/src/requests/all/promote_chat_member.rs b/src/requests/all/promote_chat_member.rs new file mode 100644 index 00000000..468b3c48 --- /dev/null +++ b/src/requests/all/promote_chat_member.rs @@ -0,0 +1,134 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, True}, + Bot, +}; + +/// Use this method to promote or demote a user in a supergroup or a channel. +/// +/// The bot must be an administrator in the chat for this to work and must have +/// the appropriate admin rights. Pass False for all boolean parameters to +/// demote a user. +/// +/// [The official docs](https://core.telegram.org/bots/api#promotechatmember). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct PromoteChatMember { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + user_id: i32, + can_change_info: Option, + can_post_messages: Option, + can_edit_messages: Option, + can_delete_messages: Option, + can_invite_users: Option, + can_restrict_members: Option, + can_pin_messages: Option, + can_promote_members: Option, +} + +#[async_trait::async_trait] +impl Request for PromoteChatMember { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "promoteChatMember", &self).await + } +} + +impl PromoteChatMember { + pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { + bot, + chat_id, + user_id, + can_change_info: None, + can_post_messages: None, + can_edit_messages: None, + can_delete_messages: None, + can_invite_users: None, + can_restrict_members: None, + can_pin_messages: None, + can_promote_members: None, + } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Unique identifier of the target user. + pub fn user_id(mut self, val: i32) -> Self { + self.user_id = val; + self + } + + /// Pass `true`, if the administrator can change chat title, photo and other + /// settings. + pub fn can_change_info(mut self, val: bool) -> Self { + self.can_change_info = Some(val); + self + } + + /// Pass `true`, if the administrator can create channel posts, channels + /// only. + pub fn can_post_messages(mut self, val: bool) -> Self { + self.can_post_messages = Some(val); + self + } + + /// Pass `true`, if the administrator can edit messages of other users and + /// can pin messages, channels only. + pub fn can_edit_messages(mut self, val: bool) -> Self { + self.can_edit_messages = Some(val); + self + } + + /// Pass `true`, if the administrator can delete messages of other users. + pub fn can_delete_messages(mut self, val: bool) -> Self { + self.can_delete_messages = Some(val); + self + } + + /// Pass `true`, if the administrator can invite new users to the chat. + pub fn can_invite_users(mut self, val: bool) -> Self { + self.can_invite_users = Some(val); + self + } + + /// Pass `true`, if the administrator can restrict, ban or unban chat + /// members. + pub fn can_restrict_members(mut self, val: bool) -> Self { + self.can_restrict_members = Some(val); + self + } + + /// Pass `true`, if the administrator can pin messages, supergroups only. + pub fn can_pin_messages(mut self, val: bool) -> Self { + self.can_pin_messages = Some(val); + self + } + + /// Pass `true`, if the administrator can add new administrators with a + /// subset of his own privileges or demote administrators that he has + /// promoted, directly or indirectly (promoted by administrators that were + /// appointed by him). + pub fn can_promote_members(mut self, val: bool) -> Self { + self.can_promote_members = Some(val); + self + } +} diff --git a/src/requests/all/restrict_chat_member.rs b/src/requests/all/restrict_chat_member.rs new file mode 100644 index 00000000..6b825b91 --- /dev/null +++ b/src/requests/all/restrict_chat_member.rs @@ -0,0 +1,76 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, ChatPermissions, True}, + Bot, +}; + +/// Use this method to restrict a user in a supergroup. +/// +/// The bot must be an administrator in the supergroup for this to work and must +/// have the appropriate admin rights. Pass `true` for all permissions to lift +/// restrictions from a user. +/// +/// [The official docs](https://core.telegram.org/bots/api#restrictchatmember). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct RestrictChatMember { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + user_id: i32, + permissions: ChatPermissions, + until_date: Option, +} + +#[async_trait::async_trait] +impl Request for RestrictChatMember { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "restrictChatMember", &self).await + } +} + +impl RestrictChatMember { + pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32, permissions: ChatPermissions) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id, user_id, permissions, until_date: None } + } + + /// Unique identifier for the target chat or username of the target + /// supergroup (in the format `@supergroupusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Unique identifier of the target user. + pub fn user_id(mut self, val: i32) -> Self { + self.user_id = val; + self + } + + /// New user permissions. + pub fn permissions(mut self, val: ChatPermissions) -> Self { + self.permissions = val; + self + } + + /// Date when restrictions will be lifted for the user, unix time. + /// + /// If user is restricted for more than 366 days or less than 30 seconds + /// from the current time, they are considered to be restricted forever. + pub fn until_date(mut self, val: i32) -> Self { + self.until_date = Some(val); + self + } +} diff --git a/src/requests/all/send_animation.rs b/src/requests/all/send_animation.rs new file mode 100644 index 00000000..287ebd93 --- /dev/null +++ b/src/requests/all/send_animation.rs @@ -0,0 +1,175 @@ +use crate::{ + net, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, + Bot, +}; + +/// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video +/// without sound). +/// +/// Bots can currently send animation files of up to 50 MB in size, this limit +/// may be changed in the future. +/// +/// [The official docs](https://core.telegram.org/bots/api#sendanimation). +#[derive(Debug, Clone)] +pub struct SendAnimation { + bot: Bot, + pub chat_id: ChatId, + pub animation: InputFile, + pub duration: Option, + pub width: Option, + pub height: Option, + pub thumb: Option, + pub caption: Option, + pub parse_mode: Option, + pub disable_notification: Option, + pub reply_to_message_id: Option, + pub reply_markup: Option, +} + +#[async_trait::async_trait] +impl RequestWithFile for SendAnimation { + type Output = Message; + + async fn send(&self) -> tokio::io::Result> { + let mut builder = FormBuilder::new() + .add_text("chat_id", &self.chat_id) + .add_input_file("animation", &self.animation) + .await? + .add_text("duration", &self.duration) + .add_text("width", &self.width) + .add_text("height", &self.height) + .add_text("caption", &self.caption) + .add_text("parse_mode", &self.parse_mode) + .add_text("disable_notification", &self.disable_notification) + .add_text("reply_to_message_id", &self.reply_to_message_id) + .add_text("reply_markup", &self.reply_markup); + if let Some(thumb) = self.thumb.as_ref() { + builder = builder.add_input_file("thumb", thumb).await?; + } + Ok(net::request_multipart( + self.bot.client(), + self.bot.token(), + "sendAnimation", + builder.build(), + ) + .await) + } +} + +impl SendAnimation { + pub(crate) fn new(bot: Bot, chat_id: C, animation: InputFile) -> Self + where + C: Into, + { + Self { + bot, + chat_id: chat_id.into(), + animation, + duration: None, + width: None, + height: None, + thumb: None, + caption: None, + parse_mode: None, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, value: T) -> Self + where + T: Into, + { + self.chat_id = value.into(); + self + } + + /// Animation to send. + pub fn animation(mut self, val: InputFile) -> Self { + self.animation = val; + self + } + + /// Duration of sent animation in seconds. + pub fn duration(mut self, value: u32) -> Self { + self.duration = Some(value); + self + } + + /// Animation width. + pub fn width(mut self, value: u32) -> Self { + self.width = Some(value); + self + } + + /// Animation height. + pub fn height(mut self, value: u32) -> Self { + self.height = Some(value); + self + } + + /// Thumbnail of the file sent; can be ignored if thumbnail generation for + /// the file is supported server-side. + /// + /// The thumbnail should be in JPEG format and less than 200 kB in size. A + /// thumbnail‘s width and height should not exceed 320. Ignored if the + /// file is not uploaded using [`InputFile::File`]. Thumbnails can’t be + /// reused and can be only uploaded as a new file, with + /// [`InputFile::File`]. + /// + /// [`InputFile::File`]: crate::types::InputFile::File + pub fn thumb(mut self, value: InputFile) -> Self { + self.thumb = Some(value); + self + } + + /// Animation caption, `0`-`1024` characters. + pub fn caption(mut self, value: T) -> Self + where + T: Into, + { + self.caption = Some(value.into()); + self + } + + /// Send [Markdown] or [HTML], if you want Telegram apps to show + /// [bold, italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: crate::types::ParseMode::Markdown + /// [HTML]: crate::types::ParseMode::HTML + /// [bold, italic, fixed-width text or inline URLs]: + /// crate::types::ParseMode + pub fn parse_mode(mut self, value: ParseMode) -> Self { + self.parse_mode = Some(value); + self + } + + /// Sends the message silently. Users will receive a notification with no + /// sound. + pub fn disable_notification(mut self, value: bool) -> Self { + self.disable_notification = Some(value); + self + } + + /// If the message is a reply, [id] of the original message. + /// + /// [id]: crate::types::Message::id + pub fn reply_to_message_id(mut self, value: i32) -> Self { + self.reply_to_message_id = Some(value); + self + } + + /// Additional interface options. + pub fn reply_markup(mut self, value: T) -> Self + where + T: Into, + { + self.reply_markup = Some(value.into()); + self + } +} diff --git a/src/requests/all/send_audio.rs b/src/requests/all/send_audio.rs new file mode 100644 index 00000000..f2ed209f --- /dev/null +++ b/src/requests/all/send_audio.rs @@ -0,0 +1,200 @@ +use crate::{ + net, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, + Bot, +}; + +/// Use this method to send audio files, if you want Telegram clients to display +/// them in the music player. +/// +/// Your audio must be in the .MP3 or .M4A format. Bots can currently send audio +/// files of up to 50 MB in size, this limit may be changed in the future. +/// +/// For sending voice messages, use the [`Bot::send_voice`] method instead. +/// +/// [The official docs](https://core.telegram.org/bots/api#sendaudio). +/// +/// [`Bot::send_voice`]: crate::Bot::send_voice +#[derive(Debug, Clone)] +pub struct SendAudio { + bot: Bot, + chat_id: ChatId, + audio: InputFile, + caption: Option, + parse_mode: Option, + duration: Option, + performer: Option, + title: Option, + thumb: Option, + disable_notification: Option, + reply_to_message_id: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl RequestWithFile for SendAudio { + type Output = Message; + + async fn send(&self) -> tokio::io::Result> { + let mut builder = FormBuilder::new() + .add_text("chat_id", &self.chat_id) + .add_input_file("audio", &self.audio) + .await? + .add_text("caption", &self.caption) + .add_text("parse_mode", &self.parse_mode) + .add_text("duration", &self.duration) + .add_text("performer", &self.performer) + .add_text("title", &self.title) + .add_text("disable_notification", &self.disable_notification) + .add_text("reply_to_message_id", &self.reply_to_message_id) + .add_text("reply_markup", &self.reply_markup); + if let Some(thumb) = self.thumb.as_ref() { + builder = builder.add_input_file("thumb", thumb).await?; + } + Ok(net::request_multipart( + self.bot.client(), + self.bot.token(), + "sendAudio", + builder.build(), + ) + .await) + } +} + +impl SendAudio { + pub(crate) fn new(bot: Bot, chat_id: C, audio: InputFile) -> Self + where + C: Into, + { + Self { + bot, + chat_id: chat_id.into(), + audio, + caption: None, + parse_mode: None, + duration: None, + performer: None, + title: None, + thumb: None, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Audio file to send. + /// + /// Pass [`InputFile::File`] to send a file that exists on + /// the Telegram servers (recommended), pass an [`InputFile::Url`] for + /// Telegram to get a .webp file from the Internet, or upload a new one + /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. + /// + /// [`InputFile::File`]: crate::types::InputFile::File + /// [`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 audio(mut self, val: InputFile) -> Self { + self.audio = val; + self + } + + /// Audio caption, 0-1024 characters. + pub fn caption(mut self, val: T) -> Self + where + T: Into, + { + self.caption = Some(val.into()); + self + } + + /// Send [Markdown] or [HTML], if you want Telegram apps to show + /// [bold, italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: crate::types::ParseMode::Markdown + /// [HTML]: crate::types::ParseMode::HTML + /// [bold, italic, fixed-width text or inline URLs]: + /// crate::types::ParseMode + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + /// Duration of the audio in seconds. + pub fn duration(mut self, val: i32) -> Self { + self.duration = Some(val); + self + } + + /// Performer. + pub fn performer(mut self, val: T) -> Self + where + T: Into, + { + self.performer = Some(val.into()); + self + } + + /// Track name. + pub fn title(mut self, val: T) -> Self + where + T: Into, + { + self.title = Some(val.into()); + self + } + + /// Thumbnail of the file sent; can be ignored if thumbnail generation for + /// the file is supported server-side. + /// + /// The thumbnail should be in JPEG format and less than 200 kB in size. A + /// thumbnail‘s width and height should not exceed 320. Ignored if the + /// file is not uploaded using `multipart/form-data`. Thumbnails can’t + /// be reused and can be only uploaded as a new file, so you can pass + /// `attach://` if the thumbnail was uploaded using + /// `multipart/form-data` under ``. [More info on + /// Sending Files Âģ]. + /// + /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files + pub fn thumb(mut self, val: InputFile) -> Self { + self.thumb = Some(val); + self + } + + /// Sends the message [silently]. Users will receive a notification with no + /// sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_notification(mut self, val: bool) -> Self { + self.disable_notification = Some(val); + self + } + + /// If the message is a reply, ID of the original message. + pub fn reply_to_message_id(mut self, val: i32) -> Self { + self.reply_to_message_id = Some(val); + self + } + + /// Additional interface options. A JSON-serialized object for an [inline + /// keyboard], [custom reply keyboard], instructions to remove reply + /// keyboard or to force a reply from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/send_chat_action.rs b/src/requests/all/send_chat_action.rs new file mode 100644 index 00000000..5fe853b2 --- /dev/null +++ b/src/requests/all/send_chat_action.rs @@ -0,0 +1,105 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, True}, + Bot, +}; + +/// Use this method when you need to tell the user that something is happening +/// on the bot's side. +/// +/// The status is set for 5 seconds or less (when a message arrives from your +/// bot, Telegram clients clear its typing status). +/// +/// ## Note +/// Example: The [ImageBot] needs some time to process a request and upload the +/// image. Instead of sending a text message along the lines of “Retrieving +/// image, please waitâ€Ļ”, the bot may use [`Bot::send_chat_action`] with `action +/// = upload_photo`. The user will see a `sending photo` status for the bot. +/// +/// We only recommend using this method when a response from the bot will take a +/// **noticeable** amount of time to arrive. +/// +/// [ImageBot]: https://t.me/imagebot +/// [`Bot::send_chat_action`]: crate::Bot::send_chat_action +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct SendChatAction { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + action: SendChatActionKind, +} + +/// A type of action used in [`SendChatAction`]. +/// +/// [`SendChatAction`]: crate::requests::SendChatAction +#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum SendChatActionKind { + /// For [text messages](crate::Bot::send_message). + Typing, + + /// For [photos](crate::Bot::send_photo). + UploadPhoto, + + /// For [videos](crate::Bot::send_video). + RecordVideo, + + /// For [videos](crate::Bot::send_video). + UploadVideo, + + /// For [audio files](crate::Bot::send_audio). + RecordAudio, + + /// For [audio files](crate::Bot::send_audio). + UploadAudio, + + /// For [general files](crate::Bot::send_document). + UploadDocument, + + /// For [location data](crate::Bot::send_location). + FindLocation, + + /// For [video notes](crate::Bot::send_video_note). + RecordVideoNote, + + /// For [video notes](crate::Bot::send_video_note). + UploadVideoNote, +} + +#[async_trait::async_trait] +impl Request for SendChatAction { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "sendChatAction", &self).await + } +} + +impl SendChatAction { + pub(crate) fn new(bot: Bot, chat_id: C, action: SendChatActionKind) -> Self + where + C: Into, + { + Self { bot, chat_id: chat_id.into(), action } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Type of action to broadcast. + pub fn action(mut self, val: SendChatActionKind) -> Self { + self.action = val; + self + } +} diff --git a/src/requests/all/send_contact.rs b/src/requests/all/send_contact.rs new file mode 100644 index 00000000..ef3ff034 --- /dev/null +++ b/src/requests/all/send_contact.rs @@ -0,0 +1,129 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, Message, ReplyMarkup}, + Bot, +}; + +/// Use this method to send phone contacts. +/// +/// [The official docs](https://core.telegram.org/bots/api#sendcontact). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct SendContact { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + phone_number: String, + first_name: String, + last_name: Option, + vcard: Option, + disable_notification: Option, + reply_to_message_id: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for SendContact { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "sendContact", &self).await + } +} + +impl SendContact { + pub(crate) fn new(bot: Bot, chat_id: C, phone_number: P, first_name: F) -> Self + where + C: Into, + P: Into, + F: Into, + { + let chat_id = chat_id.into(); + let phone_number = phone_number.into(); + let first_name = first_name.into(); + Self { + bot, + chat_id, + phone_number, + first_name, + last_name: None, + vcard: None, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Contact's phone number. + pub fn phone_number(mut self, val: T) -> Self + where + T: Into, + { + self.phone_number = val.into(); + self + } + + /// Contact's first name. + pub fn first_name(mut self, val: T) -> Self + where + T: Into, + { + self.first_name = val.into(); + self + } + + /// Contact's last name. + pub fn last_name(mut self, val: T) -> Self + where + T: Into, + { + self.last_name = Some(val.into()); + self + } + + /// Additional data about the contact in the form of a [vCard], 0-2048 + /// bytes. + /// + /// [vCard]: https://en.wikipedia.org/wiki/VCard + pub fn vcard(mut self, val: T) -> Self + where + T: Into, + { + self.vcard = Some(val.into()); + self + } + + /// Sends the message [silently]. Users will receive a notification with no + /// sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_notification(mut self, val: bool) -> Self { + self.disable_notification = Some(val); + self + } + + /// If the message is a reply, ID of the original message. + pub fn reply_to_message_id(mut self, val: i32) -> Self { + self.reply_to_message_id = Some(val); + self + } + + /// Additional interface options. + pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/send_dice.rs b/src/requests/all/send_dice.rs new file mode 100644 index 00000000..663d7001 --- /dev/null +++ b/src/requests/all/send_dice.rs @@ -0,0 +1,94 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, DiceEmoji, Message, ReplyMarkup}, + Bot, +}; + +/// Use this method to send an animated emoji that will display a random value. +/// +/// [The official docs](https://core.telegram.org/bots/api#senddice). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct SendDice { + #[serde(skip_serializing)] + bot: Bot, + + chat_id: ChatId, + #[serde(flatten)] + emoji: Option, + disable_notification: Option, + reply_to_message_id: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for SendDice { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "sendDice", &self).await + } +} + +impl SendDice { + pub(crate) fn new(bot: Bot, chat_id: C) -> Self + where + C: Into, + { + Self { + bot, + chat_id: chat_id.into(), + emoji: None, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, value: T) -> Self + where + T: Into, + { + self.chat_id = value.into(); + self + } + + /// Emoji on which the dice throw animation is based. + pub fn emoji(mut self, val: DiceEmoji) -> Self { + self.emoji = Some(val); + self + } + + /// Sends the message [silently]. Users will receive a notification with no + /// sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_notification(mut self, value: bool) -> Self { + self.disable_notification = Some(value); + self + } + + /// If the message is a reply, ID of the original message. + pub fn reply_to_message_id(mut self, value: i32) -> Self { + self.reply_to_message_id = Some(value); + self + } + + /// Additional interface options. + /// + /// A JSON-serialized object for an [inline keyboard], [custom reply + /// keyboard], instructions to remove reply keyboard or to force a reply + /// from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/send_document.rs b/src/requests/all/send_document.rs new file mode 100644 index 00000000..a3b6ff6f --- /dev/null +++ b/src/requests/all/send_document.rs @@ -0,0 +1,154 @@ +use crate::{ + net, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, + Bot, +}; + +/// Use this method to send general files. +/// +/// Bots can currently send files of any type of up to 50 MB in size, this limit +/// may be changed in the future. +/// +/// [The official docs](https://core.telegram.org/bots/api#senddocument). +#[derive(Debug, Clone)] +pub struct SendDocument { + bot: Bot, + chat_id: ChatId, + document: InputFile, + thumb: Option, + caption: Option, + parse_mode: Option, + disable_notification: Option, + reply_to_message_id: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl RequestWithFile for SendDocument { + type Output = Message; + + async fn send(&self) -> tokio::io::Result> { + let mut builder = FormBuilder::new() + .add_text("chat_id", &self.chat_id) + .add_input_file("document", &self.document) + .await? + .add_text("caption", &self.caption) + .add_text("parse_mode", &self.parse_mode) + .add_text("disable_notification", &self.disable_notification) + .add_text("reply_to_message_id", &self.reply_to_message_id) + .add_text("reply_markup", &self.reply_markup); + if let Some(thumb) = self.thumb.as_ref() { + builder = builder.add_input_file("thumb", thumb).await?; + } + Ok(net::request_multipart( + self.bot.client(), + self.bot.token(), + "sendDocument", + builder.build(), + ) + .await) + } +} + +impl SendDocument { + pub(crate) fn new(bot: Bot, chat_id: C, document: InputFile) -> Self + where + C: Into, + { + Self { + bot, + chat_id: chat_id.into(), + document, + thumb: None, + caption: None, + parse_mode: None, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// File to send. + /// + /// Pass a file_id as String to send a file that exists on the + /// Telegram servers (recommended), pass an HTTP URL as a String for + /// Telegram to get a file from the Internet, or upload a new one using + /// `multipart/form-data`. [More info on Sending Files Âģ]. + /// + /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files + pub fn document(mut self, val: InputFile) -> Self { + self.document = val; + self + } + + /// Thumbnail of the file sent; can be ignored if thumbnail generation for + /// the file is supported server-side. + /// + /// The thumbnail should be in JPEG format and less than 200 kB in size. A + /// thumbnail‘s width and height should not exceed 320. Ignored if the + /// file is not uploaded using `multipart/form-data`. Thumbnails can’t + /// be reused and can be only uploaded as a new file, so you can pass + /// “attach://” if the thumbnail was uploaded using + /// `multipart/form-data` under ``. [More info on + /// Sending Files Âģ]. + /// + /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files + pub fn thumb(mut self, val: InputFile) -> Self { + self.thumb = Some(val); + self + } + + /// Document caption (may also be used when resending documents by + /// `file_id`), 0-1024 characters. + pub fn caption(mut self, val: T) -> Self + where + T: Into, + { + self.caption = Some(val.into()); + self + } + + /// Send [Markdown] or [HTML], if you want Telegram apps to show + /// [bold, italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: crate::types::ParseMode::Markdown + /// [HTML]: crate::types::ParseMode::HTML + /// [bold, italic, fixed-width text or inline URLs]: + /// crate::types::ParseMode + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + /// Sends the message [silently]. Users will receive a notification with no + /// sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_notification(mut self, val: bool) -> Self { + self.disable_notification = Some(val); + self + } + + /// If the message is a reply, ID of the original message. + pub fn reply_to_message_id(mut self, val: i32) -> Self { + self.reply_to_message_id = Some(val); + self + } + + /// Additional interface options. + pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/send_game.rs b/src/requests/all/send_game.rs new file mode 100644 index 00000000..03a0b0e2 --- /dev/null +++ b/src/requests/all/send_game.rs @@ -0,0 +1,92 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{InlineKeyboardMarkup, Message}, + Bot, +}; + +/// Use this method to send a game. +/// +/// [The official docs](https://core.telegram.org/bots/api#sendgame). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct SendGame { + #[serde(skip_serializing)] + bot: Bot, + chat_id: i32, + game_short_name: String, + disable_notification: Option, + reply_to_message_id: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for SendGame { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "sendGame", &self).await + } +} + +impl SendGame { + pub(crate) fn new(bot: Bot, chat_id: i32, game_short_name: G) -> Self + where + G: Into, + { + let game_short_name = game_short_name.into(); + Self { + bot, + chat_id, + game_short_name, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + } + } + + /// Unique identifier for the target chat. + pub fn chat_id(mut self, val: i32) -> Self { + self.chat_id = val; + self + } + + /// Short name of the game, serves as the unique identifier for the game. + /// Set up your games via [@Botfather]. + /// + /// [@Botfather]: https://t.me/botfather + pub fn game_short_name(mut self, val: T) -> Self + where + T: Into, + { + self.game_short_name = val.into(); + self + } + + /// Sends the message [silently]. Users will receive a notification with no + /// sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_notification(mut self, val: bool) -> Self { + self.disable_notification = Some(val); + self + } + + /// If the message is a reply, ID of the original message. + pub fn reply_to_message_id(mut self, val: i32) -> Self { + self.reply_to_message_id = Some(val); + self + } + + /// A JSON-serialized object for an [inline keyboard]. If empty, one `Play + /// game_title` button will be shown. If not empty, the first button must + /// launch the game. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/send_invoice.rs b/src/requests/all/send_invoice.rs new file mode 100644 index 00000000..8466cdba --- /dev/null +++ b/src/requests/all/send_invoice.rs @@ -0,0 +1,299 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{InlineKeyboardMarkup, LabeledPrice, Message}, + Bot, +}; + +/// Use this method to send invoices. +/// +/// [The official docs](https://core.telegram.org/bots/api#sendinvoice). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct SendInvoice { + #[serde(skip_serializing)] + bot: Bot, + chat_id: i32, + title: String, + description: String, + payload: String, + provider_token: String, + start_parameter: String, + currency: String, + prices: Vec, + provider_data: Option, + photo_url: Option, + photo_size: Option, + photo_width: Option, + photo_height: Option, + need_name: Option, + need_phone_number: Option, + need_email: Option, + need_shipping_address: Option, + send_phone_number_to_provider: Option, + send_email_to_provider: Option, + is_flexible: Option, + disable_notification: Option, + reply_to_message_id: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for SendInvoice { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "sendInvoice", &self).await + } +} + +impl SendInvoice { + #[allow(clippy::too_many_arguments)] + pub(crate) fn new( + bot: Bot, + chat_id: i32, + title: T, + description: D, + payload: Pl, + provider_token: Pt, + start_parameter: S, + currency: C, + prices: Pr, + ) -> Self + where + T: Into, + D: Into, + Pl: Into, + Pt: Into, + S: Into, + C: Into, + Pr: Into>, + { + let title = title.into(); + let description = description.into(); + let payload = payload.into(); + let provider_token = provider_token.into(); + let start_parameter = start_parameter.into(); + let currency = currency.into(); + let prices = prices.into(); + Self { + bot, + chat_id, + title, + description, + payload, + provider_token, + start_parameter, + currency, + prices, + provider_data: None, + photo_url: None, + photo_size: None, + photo_width: None, + photo_height: None, + need_name: None, + need_phone_number: None, + need_email: None, + need_shipping_address: None, + send_phone_number_to_provider: None, + send_email_to_provider: None, + is_flexible: None, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + } + } + + /// Unique identifier for the target private chat. + pub fn chat_id(mut self, val: i32) -> Self { + self.chat_id = val; + self + } + + /// Product name, 1-32 characters. + pub fn title(mut self, val: T) -> Self + where + T: Into, + { + self.title = val.into(); + self + } + + /// Product description, 1-255 characters. + pub fn description(mut self, val: T) -> Self + where + T: Into, + { + self.description = val.into(); + self + } + + /// Bot-defined invoice payload, 1-128 bytes. This will not be displayed to + /// the user, use for your internal processes. + pub fn payload(mut self, val: T) -> Self + where + T: Into, + { + self.payload = val.into(); + self + } + + /// Payments provider token, obtained via [@Botfather]. + /// + /// [@Botfather]: https://t.me/botfather + pub fn provider_token(mut self, val: T) -> Self + where + T: Into, + { + self.provider_token = val.into(); + self + } + + /// Unique deep-linking parameter that can be used to generate this invoice + /// when used as a start parameter. + pub fn start_parameter(mut self, val: T) -> Self + where + T: Into, + { + self.start_parameter = val.into(); + self + } + + /// Three-letter ISO 4217 currency code, see [more on currencies]. + /// + /// [more on currencies]: https://core.telegram.org/bots/payments#supported-currencies + pub fn currency(mut self, val: T) -> Self + where + T: Into, + { + self.currency = val.into(); + self + } + + /// Price breakdown, a list of components (e.g. product price, tax, + /// discount, delivery cost, delivery tax, bonus, etc.). + pub fn prices(mut self, val: T) -> Self + where + T: Into>, + { + self.prices = val.into(); + self + } + + /// JSON-encoded data about the invoice, which will be shared with the + /// payment provider. + /// + /// A detailed description of required fields should be provided by the + /// payment provider. + pub fn provider_data(mut self, val: T) -> Self + where + T: Into, + { + self.provider_data = Some(val.into()); + self + } + + /// URL of the product photo for the invoice. + /// + /// Can be a photo of the goods or a marketing image for a service. People + /// like it better when they see what they are paying for. + pub fn photo_url(mut self, val: T) -> Self + where + T: Into, + { + self.photo_url = Some(val.into()); + self + } + + /// Photo size. + pub fn photo_size(mut self, val: i32) -> Self { + self.photo_size = Some(val); + self + } + + /// Photo width. + pub fn photo_width(mut self, val: i32) -> Self { + self.photo_width = Some(val); + self + } + + /// Photo height. + pub fn photo_height(mut self, val: i32) -> Self { + self.photo_height = Some(val); + self + } + + /// Pass `true`, if you require the user's full name to complete the order. + pub fn need_name(mut self, val: bool) -> Self { + self.need_name = Some(val); + self + } + + /// Pass `true`, if you require the user's phone number to complete the + /// order. + pub fn need_phone_number(mut self, val: bool) -> Self { + self.need_phone_number = Some(val); + self + } + + /// Pass `true`, if you require the user's email address to complete the + /// order. + pub fn need_email(mut self, val: bool) -> Self { + self.need_email = Some(val); + self + } + + /// Pass `true`, if you require the user's shipping address to complete the + /// order. + pub fn need_shipping_address(mut self, val: bool) -> Self { + self.need_shipping_address = Some(val); + self + } + + /// Pass `true`, if user's phone number should be sent to provider. + pub fn send_phone_number_to_provider(mut self, val: bool) -> Self { + self.send_phone_number_to_provider = Some(val); + self + } + + /// Pass `true`, if user's email address should be sent to provider. + pub fn send_email_to_provider(mut self, val: bool) -> Self { + self.send_email_to_provider = Some(val); + self + } + + /// Pass `true`, if the final price depends on the shipping method. + #[allow(clippy::wrong_self_convention)] + pub fn is_flexible(mut self, val: bool) -> Self { + self.is_flexible = Some(val); + self + } + + /// Sends the message [silently]. Users will receive a notification with no + /// sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_notification(mut self, val: bool) -> Self { + self.disable_notification = Some(val); + self + } + + /// If the message is a reply, ID of the original message. + pub fn reply_to_message_id(mut self, val: i32) -> Self { + self.reply_to_message_id = Some(val); + self + } + + /// A JSON-serialized object for an [inline keyboard]. + /// + /// If empty, one 'Pay `total price`' button will be shown. If not empty, + /// the first button must be a Pay button. + /// + /// [inlint keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/send_location.rs b/src/requests/all/send_location.rs new file mode 100644 index 00000000..20201ad1 --- /dev/null +++ b/src/requests/all/send_location.rs @@ -0,0 +1,110 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, Message, ReplyMarkup}, + Bot, +}; + +/// 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(Debug, Clone, Serialize)] +pub struct SendLocation { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + latitude: f32, + longitude: f32, + live_period: Option, + disable_notification: Option, + reply_to_message_id: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for SendLocation { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "sendLocation", &self).await + } +} + +impl SendLocation { + pub(crate) fn new(bot: Bot, chat_id: C, latitude: f32, longitude: f32) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { + bot, + chat_id, + latitude, + longitude, + live_period: None, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Latitude of the location. + pub fn latitude(mut self, val: f32) -> Self { + self.latitude = val; + self + } + + /// Longitude of the location. + pub fn longitude(mut self, val: f32) -> Self { + self.longitude = val; + self + } + + /// Period in seconds for which the location will be updated (see [Live + /// Locations], should be between 60 and 86400). + /// + /// [Live Locations]: https://telegram.org/blog/live-locations + pub fn live_period(mut self, val: i64) -> Self { + self.live_period = Some(val); + self + } + + /// Sends the message [silently]. Users will receive a notification with no + /// sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_notification(mut self, val: bool) -> Self { + self.disable_notification = Some(val); + self + } + + /// If the message is a reply, ID of the original message. + pub fn reply_to_message_id(mut self, val: i32) -> Self { + self.reply_to_message_id = Some(val); + self + } + + /// A JSON-serialized object for an [inline keyboard]. + /// + /// If empty, one 'Pay `total price`' button will be shown. If not empty, + /// the first button must be a Pay button. + /// + /// [inlint keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/send_media_group.rs b/src/requests/all/send_media_group.rs new file mode 100644 index 00000000..7484fc62 --- /dev/null +++ b/src/requests/all/send_media_group.rs @@ -0,0 +1,85 @@ +use crate::{ + net, + requests::{form_builder::FormBuilder, Request, ResponseResult}, + types::{ChatId, InputMedia, Message}, + Bot, +}; + +/// 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(Debug, Clone)] +pub struct SendMediaGroup { + bot: Bot, + chat_id: ChatId, + media: Vec, // TODO: InputMediaPhoto and InputMediaVideo + disable_notification: Option, + reply_to_message_id: Option, +} + +#[async_trait::async_trait] +impl Request for SendMediaGroup { + type Output = Vec; + + async fn send(&self) -> ResponseResult> { + net::request_multipart( + self.bot.client(), + self.bot.token(), + "sendMediaGroup", + FormBuilder::new() + .add_text("chat_id", &self.chat_id) + .add_text("media", &self.media) + .add_text("disable_notification", &self.disable_notification) + .add_text("reply_to_message_id", &self.reply_to_message_id) + .build(), + ) + .await + } +} + +impl SendMediaGroup { + pub(crate) fn new(bot: Bot, chat_id: C, media: M) -> Self + where + C: Into, + M: Into>, + { + let chat_id = chat_id.into(); + let media = media.into(); + Self { bot, chat_id, media, disable_notification: None, reply_to_message_id: None } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// A JSON-serialized array describing photos and videos to be sent, must + /// include 2–10 items. + pub fn media(mut self, val: T) -> Self + where + T: Into>, + { + self.media = val.into(); + self + } + + /// Sends the message [silently]. Users will receive a notification with no + /// sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_notification(mut self, val: bool) -> Self { + self.disable_notification = Some(val); + self + } + + /// If the messages are a reply, ID of the original message. + pub fn reply_to_message_id(mut self, val: i32) -> Self { + self.reply_to_message_id = Some(val); + self + } +} diff --git a/src/requests/all/send_message.rs b/src/requests/all/send_message.rs new file mode 100644 index 00000000..29525cb9 --- /dev/null +++ b/src/requests/all/send_message.rs @@ -0,0 +1,121 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, Message, ParseMode, ReplyMarkup}, + Bot, +}; + +/// Use this method to send text messages. +/// +/// [The official docs](https://core.telegram.org/bots/api#sendmessage). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct SendMessage { + #[serde(skip_serializing)] + bot: Bot, + pub chat_id: ChatId, + pub text: String, + pub parse_mode: Option, + pub disable_web_page_preview: Option, + pub disable_notification: Option, + pub reply_to_message_id: Option, + pub reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for SendMessage { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "sendMessage", &self).await + } +} + +impl SendMessage { + pub(crate) fn new(bot: Bot, chat_id: C, text: T) -> Self + where + C: Into, + T: Into, + { + Self { + bot, + chat_id: chat_id.into(), + text: text.into(), + parse_mode: None, + disable_web_page_preview: None, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, value: T) -> Self + where + T: Into, + { + self.chat_id = value.into(); + self + } + + /// Text of the message to be sent. + pub fn text(mut self, value: T) -> Self + where + T: Into, + { + self.text = value.into(); + self + } + + /// Send [Markdown] or [HTML], if you want Telegram apps to show + /// [bold, italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: crate::types::ParseMode::Markdown + /// [HTML]: crate::types::ParseMode::HTML + /// [bold, italic, fixed-width text or inline URLs]: + /// crate::types::ParseMode + pub fn parse_mode(mut self, value: ParseMode) -> Self { + self.parse_mode = Some(value); + self + } + + /// Disables link previews for links in this message. + pub fn disable_web_page_preview(mut self, value: bool) -> Self { + self.disable_web_page_preview = Some(value); + self + } + + /// Sends the message [silently]. Users will receive a notification with no + /// sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_notification(mut self, value: bool) -> Self { + self.disable_notification = Some(value); + self + } + + /// If the message is a reply, ID of the original message. + pub fn reply_to_message_id(mut self, value: i32) -> Self { + self.reply_to_message_id = Some(value); + self + } + + /// Additional interface options. + /// + /// A JSON-serialized object for an [inline keyboard], [custom reply + /// keyboard], instructions to remove reply keyboard or to force a reply + /// from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub fn reply_markup(mut self, value: T) -> Self + where + T: Into, + { + self.reply_markup = Some(value.into()); + self + } +} diff --git a/src/requests/all/send_photo.rs b/src/requests/all/send_photo.rs new file mode 100644 index 00000000..4027f000 --- /dev/null +++ b/src/requests/all/send_photo.rs @@ -0,0 +1,138 @@ +use crate::{ + net, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, + Bot, +}; + +/// Use this method to send photos. +/// +/// [The official docs](https://core.telegram.org/bots/api#sendphoto). +#[derive(Debug, Clone)] +pub struct SendPhoto { + bot: Bot, + chat_id: ChatId, + photo: InputFile, + caption: Option, + parse_mode: Option, + disable_notification: Option, + reply_to_message_id: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl RequestWithFile for SendPhoto { + type Output = Message; + + async fn send(&self) -> tokio::io::Result> { + Ok(net::request_multipart( + self.bot.client(), + self.bot.token(), + "sendPhoto", + FormBuilder::new() + .add_text("chat_id", &self.chat_id) + .add_input_file("photo", &self.photo) + .await? + .add_text("caption", &self.caption) + .add_text("parse_mode", &self.parse_mode) + .add_text("disable_notification", &self.disable_notification) + .add_text("reply_to_message_id", &self.reply_to_message_id) + .add_text("reply_markup", &self.reply_markup) + .build(), + ) + .await) + } +} + +impl SendPhoto { + pub(crate) fn new(bot: Bot, chat_id: C, photo: InputFile) -> Self + where + C: Into, + { + Self { + bot, + chat_id: chat_id.into(), + photo, + caption: None, + parse_mode: None, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Photo to send. + /// + /// Pass [`InputFile::File`] to send a photo that exists on + /// the Telegram servers (recommended), pass an [`InputFile::Url`] for + /// Telegram to get a .webp file from the Internet, or upload a new one + /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. + /// + /// [`InputFile::File`]: crate::types::InputFile::File + /// [`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 photo(mut self, val: InputFile) -> Self { + self.photo = val; + self + } + + ///Photo caption (may also be used when resending photos by file_id), + /// 0-1024 characters. + pub fn caption(mut self, val: T) -> Self + where + T: Into, + { + self.caption = Some(val.into()); + self + } + + /// Send [Markdown] or [HTML], if you want Telegram apps to show + /// [bold, italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: crate::types::ParseMode::Markdown + /// [HTML]: crate::types::ParseMode::HTML + /// [bold, italic, fixed-width text or inline URLs]: + /// crate::types::ParseMode + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + /// Sends the message [silently]. Users will receive a notification with no + /// sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_notification(mut self, val: bool) -> Self { + self.disable_notification = Some(val); + self + } + + /// If the message is a reply, ID of the original message. + pub fn reply_to_message_id(mut self, val: i32) -> Self { + self.reply_to_message_id = Some(val); + self + } + + /// Additional interface options. A JSON-serialized object for an [inline + /// keyboard], [custom reply keyboard], instructions to remove reply + /// keyboard or to force a reply from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/send_poll.rs b/src/requests/all/send_poll.rs new file mode 100644 index 00000000..3dd412a1 --- /dev/null +++ b/src/requests/all/send_poll.rs @@ -0,0 +1,217 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, Message, ParseMode, PollType, ReplyMarkup}, + Bot, +}; + +/// 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(Debug, Clone, Serialize)] +pub struct SendPoll { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + question: String, + options: Vec, + is_anonymous: Option, + poll_type: Option, + allows_multiple_answers: Option, + correct_option_id: Option, + explanation: Option, + explanation_parse_mode: Option, + open_period: Option, + close_date: Option, + is_closed: Option, + disable_notification: Option, + reply_to_message_id: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for SendPoll { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "sendPoll", &self).await + } +} + +impl SendPoll { + pub(crate) fn new(bot: Bot, chat_id: C, question: Q, options: O) -> Self + where + C: Into, + Q: Into, + O: Into>, + { + let chat_id = chat_id.into(); + let question = question.into(); + let options = options.into(); + Self { + bot, + chat_id, + question, + options, + is_anonymous: None, + poll_type: None, + allows_multiple_answers: None, + correct_option_id: None, + explanation: None, + explanation_parse_mode: None, + is_closed: None, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + close_date: None, + open_period: None, + } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + /// + /// A native poll can't be sent to a private chat. + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Poll question, 1-255 characters. + pub fn question(mut self, val: T) -> Self + where + T: Into, + { + self.question = val.into(); + self + } + + /// List of answer options, 2-10 strings 1-100 characters each. + pub fn options(mut self, val: T) -> Self + where + T: Into>, + { + self.options = val.into(); + self + } + + /// `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, + { + self.is_anonymous = Some(val.into()); + self + } + + /// Poll type, `quiz` or `regular`, defaults to `regular`. + pub fn poll_type(mut self, val: PollType) -> Self { + self.poll_type = Some(val); + self + } + + /// `true`, if the poll allows multiple answers, ignored for polls in quiz + /// mode. + /// + /// Defaults to `false`. + pub fn allows_multiple_answers(mut self, val: T) -> Self + where + T: Into, + { + self.allows_multiple_answers = Some(val.into()); + self + } + + /// 0-based identifier of the correct answer option, required for polls in + /// quiz mode. + pub fn correct_option_id(mut self, val: T) -> Self + where + T: Into, + { + self.correct_option_id = Some(val.into()); + self + } + + /// Text that is shown when a user chooses an incorrect answer or taps on + /// the lamp icon in a quiz-style poll, 0-200 characters with at most 2 line + /// feeds after entities parsing. + pub fn explanation(mut self, val: T) -> Self + where + T: Into, + { + self.explanation = Some(val.into()); + self + } + + /// Mode for parsing entities in the explanation. See [formatting options] + /// for more details. + /// + /// [formatting options]: https://core.telegram.org/bots/api#formatting-options + pub fn explanation_parse_mode(mut self, val: ParseMode) -> Self { + self.explanation_parse_mode = Some(val); + self + } + + /// Amount of time in seconds the poll will be active after creation, 5-600. + /// Can't be used together with `close_date`. + pub fn open_period(mut self, val: i32) -> Self { + self.open_period = Some(val); + self + } + + /// Point in time (Unix timestamp) when the poll will be automatically + /// closed. Must be at least 5 and no more than 600 seconds in the future. + /// Can't be used together with `open_period`. + pub fn close_date(mut self, val: i32) -> Self { + self.close_date = Some(val); + self + } + + /// 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, + { + self.is_closed = Some(val.into()); + self + } + + /// Sends the message [silently]. + /// + /// Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_notification(mut self, val: bool) -> Self { + self.disable_notification = Some(val); + self + } + + /// If the message is a reply, ID of the original message. + pub fn reply_to_message_id(mut self, val: i32) -> Self { + self.reply_to_message_id = Some(val); + self + } + + /// Additional interface options. + /// + /// A JSON-serialized object for an [inline keyboard], [custom reply + /// keyboard], instructions to remove reply keyboard or to force a reply + /// from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/send_sticker.rs b/src/requests/all/send_sticker.rs new file mode 100644 index 00000000..5e019e19 --- /dev/null +++ b/src/requests/all/send_sticker.rs @@ -0,0 +1,113 @@ +use crate::{ + net, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + types::{ChatId, InputFile, Message, ReplyMarkup}, + Bot, +}; + +/// 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(Debug, Clone)] +pub struct SendSticker { + bot: Bot, + chat_id: ChatId, + sticker: InputFile, + disable_notification: Option, + reply_to_message_id: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl RequestWithFile for SendSticker { + type Output = Message; + + async fn send(&self) -> tokio::io::Result> { + Ok(net::request_multipart( + self.bot.client(), + self.bot.token(), + "sendSticker", + FormBuilder::new() + .add_text("chat_id", &self.chat_id) + .add_input_file("sticker", &self.sticker) + .await? + .add_text("disable_notification", &self.disable_notification) + .add_text("reply_to_message_id", &self.reply_to_message_id) + .add_text("reply_markup", &self.reply_markup) + .build(), + ) + .await) + } +} + +impl SendSticker { + pub(crate) fn new(bot: Bot, chat_id: C, sticker: InputFile) -> Self + where + C: Into, + { + Self { + bot, + chat_id: chat_id.into(), + sticker, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Sticker to send. + /// + /// Pass [`InputFile::File`] to send a file that exists on + /// the Telegram servers (recommended), pass an [`InputFile::Url`] for + /// Telegram to get a .webp file from the Internet, or upload a new one + /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. + /// + /// [`InputFile::File`]: crate::types::InputFile::File + /// [`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 sticker(mut self, val: InputFile) -> Self { + self.sticker = val; + self + } + + /// Sends the message [silently]. Users will receive a notification with no + /// sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_notification(mut self, val: bool) -> Self { + self.disable_notification = Some(val); + self + } + + /// If the message is a reply, ID of the original message. + pub fn reply_to_message_id(mut self, val: i32) -> Self { + self.reply_to_message_id = Some(val); + self + } + + /// Additional interface options. + /// + /// A JSON-serialized object for an [inline keyboard], [custom reply + /// keyboard], instructions to remove reply keyboard or to force a reply + /// from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/send_venue.rs b/src/requests/all/send_venue.rs new file mode 100644 index 00000000..513ece7d --- /dev/null +++ b/src/requests/all/send_venue.rs @@ -0,0 +1,159 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, Message, ReplyMarkup}, + Bot, +}; + +/// 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(Debug, Clone, Serialize)] +pub struct SendVenue { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + latitude: f32, + longitude: f32, + title: String, + address: String, + foursquare_id: Option, + foursquare_type: Option, + disable_notification: Option, + reply_to_message_id: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for SendVenue { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "sendVenue", &self).await + } +} + +impl SendVenue { + pub(crate) fn new( + bot: Bot, + chat_id: C, + latitude: f32, + longitude: f32, + title: T, + address: A, + ) -> Self + where + C: Into, + T: Into, + A: Into, + { + let chat_id = chat_id.into(); + let title = title.into(); + let address = address.into(); + Self { + bot, + chat_id, + latitude, + longitude, + title, + address, + foursquare_id: None, + foursquare_type: None, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Latitude of the venue. + pub fn latitude(mut self, val: f32) -> Self { + self.latitude = val; + self + } + + /// Longitude of the venue. + pub fn longitude(mut self, val: f32) -> Self { + self.longitude = val; + self + } + + /// Name of the venue. + pub fn title(mut self, val: T) -> Self + where + T: Into, + { + self.title = val.into(); + self + } + + /// Address of the venue. + pub fn address(mut self, val: T) -> Self + where + T: Into, + { + self.address = val.into(); + self + } + + /// Foursquare identifier of the venue. + pub fn foursquare_id(mut self, val: T) -> Self + where + T: Into, + { + self.foursquare_id = Some(val.into()); + self + } + + /// Foursquare type of the venue, if known. + /// + /// For example, `arts_entertainment/default`, `arts_entertainment/aquarium` + /// or `food/icecream`. + pub fn foursquare_type(mut self, val: T) -> Self + where + T: Into, + { + self.foursquare_type = Some(val.into()); + self + } + + /// Sends the message [silently]. Users will receive a notification with no + /// sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_notification(mut self, val: bool) -> Self { + self.disable_notification = Some(val); + self + } + + /// If the message is a reply, ID of the original message. + pub fn reply_to_message_id(mut self, val: i32) -> Self { + self.reply_to_message_id = Some(val); + self + } + + /// Additional interface options. + /// + /// A JSON-serialized object for an [inline keyboard], [custom reply + /// keyboard], instructions to remove reply keyboard or to force a reply + /// from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/send_video.rs b/src/requests/all/send_video.rs new file mode 100644 index 00000000..30ab6a45 --- /dev/null +++ b/src/requests/all/send_video.rs @@ -0,0 +1,200 @@ +use crate::{ + net, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, + Bot, +}; + +/// Use this method to send video files, Telegram clients support mp4 videos +/// (other formats may be sent as Document). +/// +/// Bots can currently send video files of up to 50 MB in size, this +/// limit may be changed in the future. +/// +/// [The official docs](https://core.telegram.org/bots/api#sendvideo). +#[derive(Debug, Clone)] +pub struct SendVideo { + bot: Bot, + chat_id: ChatId, + video: InputFile, + duration: Option, + width: Option, + height: Option, + thumb: Option, + caption: Option, + parse_mode: Option, + supports_streaming: Option, + disable_notification: Option, + reply_to_message_id: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl RequestWithFile for SendVideo { + type Output = Message; + + async fn send(&self) -> tokio::io::Result> { + let mut builder = FormBuilder::new() + .add_text("chat_id", &self.chat_id) + .add_input_file("video", &self.video) + .await? + .add_text("duration", &self.duration) + .add_text("width", &self.width) + .add_text("height", &self.height) + .add_text("caption", &self.caption) + .add_text("parse_mode", &self.parse_mode) + .add_text("supports_streaming", &self.supports_streaming) + .add_text("disable_notification", &self.disable_notification) + .add_text("reply_to_message_id", &self.reply_to_message_id) + .add_text("reply_markup", &self.reply_markup); + if let Some(thumb) = self.thumb.as_ref() { + builder = builder.add_input_file("thumb", thumb).await?; + } + Ok(net::request_multipart( + self.bot.client(), + self.bot.token(), + "sendVideo", + builder.build(), + ) + .await) + } +} + +impl SendVideo { + pub(crate) fn new(bot: Bot, chat_id: C, video: InputFile) -> Self + where + C: Into, + { + Self { + bot, + chat_id: chat_id.into(), + video, + duration: None, + width: None, + height: None, + thumb: None, + caption: None, + parse_mode: None, + supports_streaming: None, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Video to sent. + /// + /// Pass [`InputFile::File`] to send a file that exists on + /// the Telegram servers (recommended), pass an [`InputFile::Url`] for + /// Telegram to get a .webp file from the Internet, or upload a new one + /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. + /// + /// [`InputFile::File`]: crate::types::InputFile::File + /// [`InputFile::Url`]: crate::types::InputFile::Url + /// [`InputFile::FileId`]: crate::types::InputFile::FileId + pub fn video(mut self, val: InputFile) -> Self { + self.video = val; + self + } + + /// Duration of sent video in seconds. + pub fn duration(mut self, val: i32) -> Self { + self.duration = Some(val); + self + } + + /// Video width. + pub fn width(mut self, val: i32) -> Self { + self.width = Some(val); + self + } + + /// Video height. + pub fn height(mut self, val: i32) -> Self { + self.height = Some(val); + self + } + + /// Thumbnail of the file sent; can be ignored if thumbnail generation for + /// the file is supported server-side. + /// + /// The thumbnail should be in JPEG format and less than 200 kB in size. A + /// thumbnail‘s width and height should not exceed 320. Ignored if the + /// file is not uploaded using `multipart/form-data`. Thumbnails can’t be + /// reused and can be only uploaded as a new file, so you can pass + /// `attach://` if the thumbnail was uploaded using + /// `multipart/form-data` under ``. [More info on Sending + /// Files Âģ]. + /// + /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files + pub fn thumb(mut self, val: InputFile) -> Self { + self.thumb = Some(val); + self + } + + /// Video caption (may also be used when resending videos by file_id), + /// 0-1024 characters. + pub fn caption(mut self, val: T) -> Self + where + T: Into, + { + self.caption = Some(val.into()); + self + } + + /// Send [Markdown] or [HTML], if you want Telegram apps to show + /// [bold, italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: crate::types::ParseMode::Markdown + /// [HTML]: crate::types::ParseMode::HTML + /// [bold, italic, fixed-width text or inline URLs]: + /// crate::types::ParseMode + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + /// Pass `true`, if the uploaded video is suitable for streaming. + pub fn supports_streaming(mut self, val: bool) -> Self { + self.supports_streaming = Some(val); + self + } + + /// Sends the message [silently]. Users will receive a notification with no + /// sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_notification(mut self, val: bool) -> Self { + self.disable_notification = Some(val); + self + } + + /// If the message is a reply, ID of the original message. + pub fn reply_to_message_id(mut self, val: i32) -> Self { + self.reply_to_message_id = Some(val); + self + } + + /// Additional interface options. + /// + /// A JSON-serialized object for an [inline keyboard], [custom reply + /// keyboard], instructions to remove reply keyboard or to force a reply + /// from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/send_video_note.rs b/src/requests/all/send_video_note.rs new file mode 100644 index 00000000..2ac653be --- /dev/null +++ b/src/requests/all/send_video_note.rs @@ -0,0 +1,152 @@ +use crate::{ + net, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + types::{ChatId, InputFile, Message, ReplyMarkup}, + Bot, +}; + +/// 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. +/// +/// [The official docs](https://core.telegram.org/bots/api#sendvideonote). +/// +/// [v.4.0]: https://telegram.org/blog/video-messages-and-telescope +#[derive(Debug, Clone)] +pub struct SendVideoNote { + bot: Bot, + chat_id: ChatId, + video_note: InputFile, + duration: Option, + length: Option, + thumb: Option, + disable_notification: Option, + reply_to_message_id: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl RequestWithFile for SendVideoNote { + type Output = Message; + + async fn send(&self) -> tokio::io::Result> { + let mut builder = FormBuilder::new() + .add_text("chat_id", &self.chat_id) + .add_input_file("video_note", &self.video_note) + .await? + .add_text("duration", &self.duration) + .add_text("length", &self.length) + .add_text("disable_notification", &self.disable_notification) + .add_text("reply_to_message_id", &self.reply_to_message_id) + .add_text("reply_markup", &self.reply_markup); + if let Some(thumb) = self.thumb.as_ref() { + builder = builder.add_input_file("thumb", thumb).await?; + } + Ok(net::request_multipart( + self.bot.client(), + self.bot.token(), + "sendVideoNote", + builder.build(), + ) + .await) + } +} + +impl SendVideoNote { + pub(crate) fn new(bot: Bot, chat_id: C, video_note: InputFile) -> Self + where + C: Into, + { + Self { + bot, + chat_id: chat_id.into(), + video_note, + duration: None, + length: None, + thumb: None, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Video note to send. + /// + /// Pass [`InputFile::File`] to send a file that exists on + /// the Telegram servers (recommended), pass an [`InputFile::Url`] for + /// Telegram to get a .webp file from the Internet, or upload a new one + /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. + /// + /// [`InputFile::File`]: crate::types::InputFile::File + /// [`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 video_note(mut self, val: InputFile) -> Self { + self.video_note = val; + self + } + + /// Duration of sent video in seconds. + pub fn duration(mut self, val: i32) -> Self { + self.duration = Some(val); + self + } + + /// Video width and height, i.e. diameter of the video message. + pub fn length(mut self, val: i32) -> Self { + self.length = Some(val); + self + } + + /// Thumbnail of the file sent; can be ignored if thumbnail generation for + /// the file is supported server-side. + /// + /// The thumbnail should be in JPEG format and less than 200 kB in size. A + /// thumbnail‘s width and height should not exceed 320. Ignored if the + /// file is not uploaded using `multipart/form-data`. Thumbnails can’t + /// be reused and can be only uploaded as a new file, so you can pass + /// `attach://` if the thumbnail was uploaded using + /// `multipart/form-data` under ``. [More info on + /// Sending Files Âģ]. + pub fn thumb(mut self, val: InputFile) -> Self { + self.thumb = Some(val); + self + } + + /// Sends the message [silently]. Users will receive a notification with no + /// sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_notification(mut self, val: bool) -> Self { + self.disable_notification = Some(val); + self + } + + /// If the message is a reply, ID of the original message. + pub fn reply_to_message_id(mut self, val: i32) -> Self { + self.reply_to_message_id = Some(val); + self + } + + /// Additional interface options. + /// + /// A JSON-serialized object for an [inline keyboard], [custom reply + /// keyboard], instructions to remove reply keyboard or to force a reply + /// from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/send_voice.rs b/src/requests/all/send_voice.rs new file mode 100644 index 00000000..1e50bbed --- /dev/null +++ b/src/requests/all/send_voice.rs @@ -0,0 +1,156 @@ +use crate::{ + net, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, + Bot, +}; + +/// Use this method to send audio files, if you want Telegram clients to display +/// the file as a playable voice message. +/// +/// For this to work, your audio must be in an .ogg file encoded with OPUS +/// (other formats may be sent as [`Audio`] or [`Document`]). Bots can currently +/// send voice messages of up to 50 MB in size, this limit may be changed in the +/// future. +/// +/// [The official docs](https://core.telegram.org/bots/api#sendvoice). +/// +/// [`Audio`]: crate::types::Audio +/// [`Document`]: crate::types::Document +#[derive(Debug, Clone)] +pub struct SendVoice { + bot: Bot, + chat_id: ChatId, + voice: InputFile, + caption: Option, + parse_mode: Option, + duration: Option, + disable_notification: Option, + reply_to_message_id: Option, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl RequestWithFile for SendVoice { + type Output = Message; + + async fn send(&self) -> tokio::io::Result> { + Ok(net::request_multipart( + self.bot.client(), + self.bot.token(), + "sendVoice", + FormBuilder::new() + .add_text("chat_id", &self.chat_id) + .add_input_file("voice", &self.voice) + .await? + .add_text("caption", &self.caption) + .add_text("parse_mode", &self.parse_mode) + .add_text("duration", &self.duration) + .add_text("disable_notification", &self.disable_notification) + .add_text("reply_to_message_id", &self.reply_to_message_id) + .add_text("reply_markup", &self.reply_markup) + .build(), + ) + .await) + } +} + +impl SendVoice { + pub(crate) fn new(bot: Bot, chat_id: C, voice: InputFile) -> Self + where + C: Into, + { + Self { + bot, + chat_id: chat_id.into(), + voice, + caption: None, + parse_mode: None, + duration: None, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Audio file to send. + /// + /// Pass [`InputFile::File`] to send a file that exists on + /// the Telegram servers (recommended), pass an [`InputFile::Url`] for + /// Telegram to get a .webp file from the Internet, or upload a new one + /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. + /// + /// [`InputFile::File`]: crate::types::InputFile::File + /// [`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 voice(mut self, val: InputFile) -> Self { + self.voice = val; + self + } + + /// Voice message caption, 0-1024 characters. + pub fn caption(mut self, val: T) -> Self + where + T: Into, + { + self.caption = Some(val.into()); + self + } + + /// Send [Markdown] or [HTML], if you want Telegram apps to show + /// [bold, italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: crate::types::ParseMode::Markdown + /// [HTML]: crate::types::ParseMode::HTML + /// [bold, italic, fixed-width text or inline URLs]: + /// crate::types::ParseMode + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + /// Duration of the voice message in seconds. + pub fn duration(mut self, val: i32) -> Self { + self.duration = Some(val); + self + } + + /// Sends the message [silently]. Users will receive a notification with no + /// sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_notification(mut self, val: bool) -> Self { + self.disable_notification = Some(val); + self + } + + /// If the message is a reply, ID of the original message. + pub fn reply_to_message_id(mut self, val: i32) -> Self { + self.reply_to_message_id = Some(val); + self + } + + /// Additional interface options. + /// + /// A JSON-serialized object for an [inline keyboard], [custom reply + /// keyboard], instructions to remove reply keyboard or to force a reply + /// from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/set_chat_administrator_custom_title.rs b/src/requests/all/set_chat_administrator_custom_title.rs new file mode 100644 index 00000000..a3211811 --- /dev/null +++ b/src/requests/all/set_chat_administrator_custom_title.rs @@ -0,0 +1,75 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, True}, + Bot, +}; + +/// 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(Debug, Clone, Serialize)] +pub struct SetChatAdministratorCustomTitle { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + user_id: i32, + custom_title: String, +} + +#[async_trait::async_trait] +impl Request for SetChatAdministratorCustomTitle { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json( + self.bot.client(), + self.bot.token(), + "setChatAdministratorCustomTitle", + &self, + ) + .await + } +} + +impl SetChatAdministratorCustomTitle { + pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32, custom_title: CT) -> Self + where + C: Into, + CT: Into, + { + let chat_id = chat_id.into(); + let custom_title = custom_title.into(); + Self { bot, chat_id, user_id, custom_title } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Unique identifier of the target user. + pub fn user_id(mut self, val: i32) -> Self { + self.user_id = val; + self + } + + /// New custom title for the administrator; 0-16 characters, emoji are not + /// allowed. + pub fn custom_title(mut self, val: T) -> Self + where + T: Into, + { + self.custom_title = val.into(); + self + } +} diff --git a/src/requests/all/set_chat_description.rs b/src/requests/all/set_chat_description.rs new file mode 100644 index 00000000..092c495b --- /dev/null +++ b/src/requests/all/set_chat_description.rs @@ -0,0 +1,62 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, True}, + Bot, +}; + +/// Use this method to change the description of a group, a supergroup or a +/// channel. +/// +/// The bot must be an administrator in the chat for this to work and must have +/// the appropriate admin rights. +/// +/// [The official docs](https://core.telegram.org/bots/api#setchatdescription). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct SetChatDescription { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + description: Option, +} + +#[async_trait::async_trait] +impl Request for SetChatDescription { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "setChatDescription", &self).await + } +} + +impl SetChatDescription { + pub(crate) fn new(bot: Bot, chat_id: C) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id, description: None } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// New chat description, 0-255 characters. + pub fn description(mut self, val: T) -> Self + where + T: Into, + { + self.description = Some(val.into()); + self + } +} diff --git a/src/requests/all/set_chat_permissions.rs b/src/requests/all/set_chat_permissions.rs new file mode 100644 index 00000000..aaa1881b --- /dev/null +++ b/src/requests/all/set_chat_permissions.rs @@ -0,0 +1,58 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, ChatPermissions, True}, + Bot, +}; + +/// Use this method to set default chat permissions for all members. +/// +/// The bot must be an administrator in the group or a supergroup for this to +/// work and must have the can_restrict_members admin rights. +/// +/// [The official docs](https://core.telegram.org/bots/api#setchatpermissions). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct SetChatPermissions { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + permissions: ChatPermissions, +} + +#[async_trait::async_trait] +impl Request for SetChatPermissions { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "sendChatPermissions", &self).await + } +} + +impl SetChatPermissions { + pub(crate) fn new(bot: Bot, chat_id: C, permissions: ChatPermissions) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id, permissions } + } + + /// Unique identifier for the target chat or username of the target + /// supergroup (in the format `@supergroupusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// New default chat permissions. + pub fn permissions(mut self, val: ChatPermissions) -> Self { + self.permissions = val; + self + } +} diff --git a/src/requests/all/set_chat_photo.rs b/src/requests/all/set_chat_photo.rs new file mode 100644 index 00000000..dcc5febc --- /dev/null +++ b/src/requests/all/set_chat_photo.rs @@ -0,0 +1,58 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, InputFile, True}, + Bot, +}; + +/// Use this method to set a new profile photo for the chat. +/// +/// Photos can't be changed for private chats. The bot must be an administrator +/// in the chat for this to work and must have the appropriate admin rights. +/// +/// [The official docs](https://core.telegram.org/bots/api#setchatphoto). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct SetChatPhoto { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + photo: InputFile, +} + +#[async_trait::async_trait] +impl Request for SetChatPhoto { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "setChatPhoto", &self).await + } +} + +impl SetChatPhoto { + pub(crate) fn new(bot: Bot, chat_id: C, photo: InputFile) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id, photo } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// New chat photo, uploaded using `multipart/form-data`. + pub fn photo(mut self, val: InputFile) -> Self { + self.photo = val; + self + } +} diff --git a/src/requests/all/set_chat_sticker_set.rs b/src/requests/all/set_chat_sticker_set.rs new file mode 100644 index 00000000..010a10f5 --- /dev/null +++ b/src/requests/all/set_chat_sticker_set.rs @@ -0,0 +1,64 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, True}, + Bot, +}; + +/// Use this method to set a new group sticker set for a supergroup. +/// +/// The bot must be an administrator in the chat for this to work and must have +/// the appropriate admin rights. Use the field can_set_sticker_set optionally +/// returned in getChat requests to check if the bot can use this method. +/// +/// [The official docs](https://core.telegram.org/bots/api#setchatstickerset). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct SetChatStickerSet { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + sticker_set_name: String, +} + +#[async_trait::async_trait] +impl Request for SetChatStickerSet { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "setChatStickerSet", &self).await + } +} + +impl SetChatStickerSet { + pub(crate) fn new(bot: Bot, chat_id: C, sticker_set_name: S) -> Self + where + C: Into, + S: Into, + { + let chat_id = chat_id.into(); + let sticker_set_name = sticker_set_name.into(); + Self { bot, chat_id, sticker_set_name } + } + + /// Unique identifier for the target chat or username of the target + /// supergroup (in the format `@supergroupusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Name of the sticker set to be set as the group sticker set. + pub fn sticker_set_name(mut self, val: T) -> Self + where + T: Into, + { + self.sticker_set_name = val.into(); + self + } +} diff --git a/src/requests/all/set_chat_title.rs b/src/requests/all/set_chat_title.rs new file mode 100644 index 00000000..7d6ae6d1 --- /dev/null +++ b/src/requests/all/set_chat_title.rs @@ -0,0 +1,63 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, True}, + Bot, +}; + +/// Use this method to change the title of a chat. +/// +/// Titles can't be changed for private chats. The bot must be an administrator +/// in the chat for this to work and must have the appropriate admin rights. +/// +/// [The official docs](https://core.telegram.org/bots/api#setchattitle). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct SetChatTitle { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + title: String, +} + +#[async_trait::async_trait] +impl Request for SetChatTitle { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "setChatTitle", &self).await + } +} + +impl SetChatTitle { + pub(crate) fn new(bot: Bot, chat_id: C, title: T) -> Self + where + C: Into, + T: Into, + { + let chat_id = chat_id.into(); + let title = title.into(); + Self { bot, chat_id, title } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// New chat title, 1-255 characters. + pub fn title(mut self, val: T) -> Self + where + T: Into, + { + self.title = val.into(); + self + } +} diff --git a/src/requests/all/set_game_score.rs b/src/requests/all/set_game_score.rs new file mode 100644 index 00000000..8e429e3a --- /dev/null +++ b/src/requests/all/set_game_score.rs @@ -0,0 +1,89 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{Message, TargetMessage}, + Bot, +}; + +/// Use this method to set the score of the specified user in a game. +/// +/// On success, if the message was sent by the bot, returns the edited +/// [`Message`], otherwise returns [`True`]. Returns an error, if the new score +/// is not greater than the user's current score in the chat and force is +/// `false`. +/// +/// [The official docs](https://core.telegram.org/bots/api#setgamescore). +/// +/// [`Message`]: crate::types::Message +/// [`True`]: crate::types::True +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct SetGameScore { + #[serde(skip_serializing)] + bot: Bot, + #[serde(flatten)] + target: TargetMessage, + user_id: i32, + score: i32, + force: Option, + disable_edit_message: Option, +} + +#[async_trait::async_trait] +impl Request for SetGameScore { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "setGameScore", &self).await + } +} + +impl SetGameScore { + pub(crate) fn new(bot: Bot, target: T, user_id: i32, score: i32) -> Self + where + T: Into, + { + let target = target.into(); + Self { bot, target, user_id, score, force: None, disable_edit_message: None } + } + + /// Target message, either chat id and message id or inline message id. + pub fn target(mut self, val: T) -> Self + where + T: Into, + { + self.target = val.into(); + self + } + + /// User identifier. + pub fn user_id(mut self, val: i32) -> Self { + self.user_id = val; + self + } + + /// New score, must be non-negative. + pub fn score(mut self, val: i32) -> Self { + self.score = val; + self + } + + /// Pass `true`, if the high score is allowed to decrease. + /// + /// This can be useful when fixing mistakes or banning cheaters. + pub fn force(mut self, val: bool) -> Self { + self.force = Some(val); + self + } + + /// Sends the message [silently]. Users will receive a notification with no + /// sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub fn disable_edit_message(mut self, val: bool) -> Self { + self.disable_edit_message = Some(val); + self + } +} diff --git a/src/requests/all/set_my_commands.rs b/src/requests/all/set_my_commands.rs new file mode 100644 index 00000000..831f9705 --- /dev/null +++ b/src/requests/all/set_my_commands.rs @@ -0,0 +1,50 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{BotCommand, True}, + Bot, +}; + +/// Use this method to change the list of the bot's commands. +/// +/// [The official docs](https://core.telegram.org/bots/api#setmycommands). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct SetMyCommands { + #[serde(skip_serializing)] + bot: Bot, + + commands: Vec, +} + +#[async_trait::async_trait] +impl Request for SetMyCommands { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "setMyCommands", &self).await + } +} + +impl SetMyCommands { + pub(crate) fn new(bot: Bot, commands: C) -> Self + where + C: Into>, + { + Self { bot, commands: commands.into() } + } + + /// A JSON-serialized list of bot commands to be set as the list of the + /// bot's commands. + /// + /// At most 100 commands can be specified. + pub fn commands(mut self, commands: C) -> Self + where + C: Into>, + { + self.commands = commands.into(); + self + } +} diff --git a/src/requests/all/set_sticker_position_in_set.rs b/src/requests/all/set_sticker_position_in_set.rs new file mode 100644 index 00000000..4ed78551 --- /dev/null +++ b/src/requests/all/set_sticker_position_in_set.rs @@ -0,0 +1,56 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::True, + Bot, +}; + +/// 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(Debug, Clone, Serialize)] +pub struct SetStickerPositionInSet { + #[serde(skip_serializing)] + bot: Bot, + sticker: String, + position: i32, +} + +#[async_trait::async_trait] +impl Request for SetStickerPositionInSet { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "setStickerPositionInSet", &self) + .await + } +} + +impl SetStickerPositionInSet { + pub(crate) fn new(bot: Bot, sticker: S, position: i32) -> Self + where + S: Into, + { + let sticker = sticker.into(); + Self { bot, sticker, position } + } + + /// File identifier of the sticker. + pub fn sticker(mut self, val: T) -> Self + where + T: Into, + { + self.sticker = val.into(); + self + } + + /// New sticker position in the set, zero-based. + pub fn position(mut self, val: i32) -> Self { + self.position = val; + self + } +} diff --git a/src/requests/all/set_sticker_set_thumb.rs b/src/requests/all/set_sticker_set_thumb.rs new file mode 100644 index 00000000..1f84aacd --- /dev/null +++ b/src/requests/all/set_sticker_set_thumb.rs @@ -0,0 +1,73 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{InputFile, True}, + Bot, +}; + +/// Use this method to set the thumbnail of a sticker set. Animated thumbnails +/// can be set for animated sticker sets only. +/// +/// [The official docs](https://core.telegram.org/bots/api#setstickersetthumb). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct SetStickerSetThumb { + #[serde(skip_serializing)] + bot: Bot, + name: String, + user_id: i32, + thumb: Option, +} + +#[async_trait::async_trait] +impl Request for SetStickerSetThumb { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "setStickerSetThumb", &self).await + } +} + +impl SetStickerSetThumb { + pub(crate) fn new(bot: Bot, name: S, user_id: i32) -> Self + where + S: Into, + { + Self { bot, name: name.into(), user_id, thumb: None } + } + + /// Sticker set name. + pub fn name(mut self, val: T) -> Self + where + T: Into, + { + self.name = val.into(); + self + } + + /// User identifier of the sticker set owner. + pub fn user_id(mut self, val: i32) -> Self { + self.user_id = val; + self + } + + /// A PNG image with the thumbnail, must be up to 128 kilobytes in size and + /// have width and height exactly 100px, or a TGS animation with the + /// thumbnail up to 32 kilobytes in size; see https://core.telegram.org/animated_stickers#technical-requirements + /// for animated sticker technical requirements. + /// + /// Pass [`InputFile::FileId`] as a String to send a file that already + /// exists on the Telegram servers, pass [`InputFile::Url`] for Telegram + /// to get a file from the Internet, or upload a new one using + /// multipart/form-data. More info on Sending Files Âģ. Animated sticker + /// set thumbnail can't be uploaded via HTTP URL. + /// + /// [`InputFile::FileId`]: crate::types::InputFile::FileId + /// [`InputFile::Url]: crate::types::InputFile::Url + pub fn thumb(mut self, val: InputFile) -> Self { + self.thumb = Some(val); + self + } +} diff --git a/src/requests/all/set_webhook.rs b/src/requests/all/set_webhook.rs new file mode 100644 index 00000000..c2aff792 --- /dev/null +++ b/src/requests/all/set_webhook.rs @@ -0,0 +1,114 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{AllowedUpdate, InputFile, True}, + Bot, +}; + +/// Use this method to specify a url and receive incoming updates via an +/// outgoing webhook. +/// +/// Whenever there is an update for the bot, we will send an +/// HTTPS POST request to the specified url, containing a JSON-serialized +/// [`Update`]. In case of an unsuccessful request, we will give up after a +/// reasonable amount of attempts. +/// +/// If you'd like to make sure that the Webhook request comes from Telegram, +/// we recommend using a secret path in the URL, e.g. +/// `https://www.example.com/`. Since nobody else knows your bot‘s +/// token, you can be pretty sure it’s us. +/// +/// [The official docs](https://core.telegram.org/bots/api#setwebhook). +/// +/// [`Update`]: crate::types::Update +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct SetWebhook { + #[serde(skip_serializing)] + bot: Bot, + url: String, + certificate: Option, + max_connections: Option, + allowed_updates: Option>, +} + +#[async_trait::async_trait] +impl Request for SetWebhook { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "setWebhook", &self).await + } +} + +impl SetWebhook { + pub(crate) fn new(bot: Bot, url: U) -> Self + where + U: Into, + { + let url = url.into(); + Self { bot, url, certificate: None, max_connections: None, allowed_updates: None } + } + + /// HTTPS url to send updates to. + /// + /// Use an empty string to remove webhook integration. + pub fn url(mut self, val: T) -> Self + where + T: Into, + { + self.url = val.into(); + self + } + + /// Upload your public key certificate so that the root certificate in use + /// can be checked. + /// + /// See our [self-signed guide] for details. + /// + /// [self-signed guide]: https://core.telegram.org/bots/self-signed + pub fn certificate(mut self, val: InputFile) -> Self { + self.certificate = Some(val); + self + } + + /// Maximum allowed number of simultaneous HTTPS connections to the webhook + /// for update delivery, 1-100. + /// + /// Defaults to 40. Use lower values to limit the load on your bot‘s server, + /// and higher values to increase your bot’s throughput. + pub fn max_connections(mut self, val: i32) -> Self { + self.max_connections = Some(val); + self + } + + /// List the types of updates you want your bot to receive. + /// + /// For example, specify [`AllowedUpdate::Message`], + /// [`AllowedUpdate::EditedChannelPost`], [`AllowedUpdate::CallbackQuery`] + /// to only receive updates of these types. Specify an empty list to receive + /// all updates regardless of type (default). If not specified, the + /// previous setting will be used. See [`AllowedUpdate`] for a complete list + /// of available update types. + /// + /// Please note that this parameter doesn't affect updates created before + /// the call to the [`Bot::set_webhook`], so unwanted updates may be + /// received for a short period of time. + /// + /// [`Bot::set_webhook`]: crate::Bot::set_webhook + /// [`AllowedUpdate::Message`]: crate::types::AllowedUpdate::Message + /// [`AllowedUpdate::EditedChannelPost`]: + /// crate::types::AllowedUpdate::EditedChannelPost + /// [`AllowedUpdate::CallbackQuery`]: + /// crate::types::AllowedUpdate::CallbackQuery + /// [`AllowedUpdate`]: crate::types::AllowedUpdate + pub fn allowed_updates(mut self, val: T) -> Self + where + T: Into>, + { + self.allowed_updates = Some(val.into()); + self + } +} diff --git a/src/requests/all/stop_inline_message_live_location.rs b/src/requests/all/stop_inline_message_live_location.rs new file mode 100644 index 00000000..40fb5604 --- /dev/null +++ b/src/requests/all/stop_inline_message_live_location.rs @@ -0,0 +1,62 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{InlineKeyboardMarkup, Message}, + Bot, +}; + +/// Use this method to stop updating a live location message (sent via the bot) +/// before `live_period` expires. +/// +/// On success, [`True`] is returned. +/// +/// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation). +/// +/// [`True`]: crate::types::True +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct StopInlineMessageLiveLocation { + #[serde(skip_serializing)] + bot: Bot, + inline_message_id: String, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for StopInlineMessageLiveLocation { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "stopMessageLiveLocation", &self) + .await + } +} + +impl StopInlineMessageLiveLocation { + pub(crate) fn new(bot: Bot, inline_message_id: I) -> Self + where + I: Into, + { + let inline_message_id = inline_message_id.into(); + Self { bot, inline_message_id, reply_markup: None } + } + + /// Identifier of the inline message. + pub fn inline_message_id(mut self, val: T) -> Self + where + T: Into, + { + self.inline_message_id = val.into(); + self + } + + /// A JSON-serialized object for a new [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/stop_message_live_location.rs b/src/requests/all/stop_message_live_location.rs new file mode 100644 index 00000000..892492ea --- /dev/null +++ b/src/requests/all/stop_message_live_location.rs @@ -0,0 +1,70 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, InlineKeyboardMarkup, Message}, + Bot, +}; + +/// Use this method to stop updating a live location message before +/// `live_period` expires. +/// +/// On success, the sent [`Message`] is returned. +/// +/// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation). +/// +/// [`Message`]: crate::types::Message +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct StopMessageLiveLocation { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + message_id: i32, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for StopMessageLiveLocation { + type Output = Message; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "stopMessageLiveLocation", &self) + .await + } +} + +impl StopMessageLiveLocation { + pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id, message_id, reply_markup: None } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`) + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Identifier of the message to edit + pub fn message_id(mut self, val: i32) -> Self { + self.message_id = val; + self + } + + /// A JSON-serialized object for a new [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/stop_poll.rs b/src/requests/all/stop_poll.rs new file mode 100644 index 00000000..946b4760 --- /dev/null +++ b/src/requests/all/stop_poll.rs @@ -0,0 +1,66 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, InlineKeyboardMarkup, Poll}, + Bot, +}; + +/// 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(Debug, Clone, Serialize)] +pub struct StopPoll { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + message_id: i32, + reply_markup: Option, +} + +#[async_trait::async_trait] +impl Request for StopPoll { + type Output = Poll; + + /// On success, the stopped [`Poll`] with the final results is returned. + /// + /// [`Poll`]: crate::types::Poll + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "stopPoll", &self).await + } +} +impl StopPoll { + pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id, message_id, reply_markup: None } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Identifier of the original message with the poll. + pub fn message_id(mut self, val: i32) -> Self { + self.message_id = val; + self + } + + /// A JSON-serialized object for a new [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/requests/all/unban_chat_member.rs b/src/requests/all/unban_chat_member.rs new file mode 100644 index 00000000..5aee625c --- /dev/null +++ b/src/requests/all/unban_chat_member.rs @@ -0,0 +1,58 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, True}, + Bot, +}; + +/// 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, +/// but will be able to join via link, etc. The bot must be an administrator for +/// this to work. +/// +/// [The official docs](https://core.telegram.org/bots/api#unbanchatmember). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct UnbanChatMember { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, + user_id: i32, +} + +#[async_trait::async_trait] +impl Request for UnbanChatMember { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "unbanChatMember", &self).await + } +} + +impl UnbanChatMember { + pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id, user_id } + } + + /// Unique identifier for the target group or username of the target + /// supergroup or channel (in the format `@username`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } + + /// Unique identifier of the target user. + pub fn user_id(mut self, val: i32) -> Self { + self.user_id = val; + self + } +} diff --git a/src/requests/all/unpin_chat_message.rs b/src/requests/all/unpin_chat_message.rs new file mode 100644 index 00000000..15e76a99 --- /dev/null +++ b/src/requests/all/unpin_chat_message.rs @@ -0,0 +1,52 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{ChatId, True}, + Bot, +}; + +/// Use this method to unpin a message in a group, a supergroup, or a channel. +/// +/// The bot must be an administrator in the chat for this to work and must have +/// the `can_pin_messages` admin right in the supergroup or `can_edit_messages` +/// admin right in the channel. +/// +/// [The official docs](https://core.telegram.org/bots/api#unpinchatmessage). +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] +pub struct UnpinChatMessage { + #[serde(skip_serializing)] + bot: Bot, + chat_id: ChatId, +} + +#[async_trait::async_trait] +impl Request for UnpinChatMessage { + type Output = True; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "unpinChatMessage", &self).await + } +} + +impl UnpinChatMessage { + pub(crate) fn new(bot: Bot, chat_id: C) -> Self + where + C: Into, + { + let chat_id = chat_id.into(); + Self { bot, chat_id } + } + + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`). + pub fn chat_id(mut self, val: T) -> Self + where + T: Into, + { + self.chat_id = val.into(); + self + } +} diff --git a/src/requests/all/upload_sticker_file.rs b/src/requests/all/upload_sticker_file.rs new file mode 100644 index 00000000..b09b143f --- /dev/null +++ b/src/requests/all/upload_sticker_file.rs @@ -0,0 +1,55 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{Request, ResponseResult}, + types::{File, InputFile}, + Bot, +}; + +/// 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 +/// be used multiple times). +/// +/// [The official docs](https://core.telegram.org/bots/api#uploadstickerfile). +/// +/// [`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(Debug, Clone, Serialize)] +pub struct UploadStickerFile { + #[serde(skip_serializing)] + bot: Bot, + user_id: i32, + png_sticker: InputFile, +} +#[async_trait::async_trait] +impl Request for UploadStickerFile { + type Output = File; + + async fn send(&self) -> ResponseResult { + net::request_json(self.bot.client(), self.bot.token(), "uploadStickerFile", &self).await + } +} + +impl UploadStickerFile { + pub(crate) fn new(bot: Bot, user_id: i32, png_sticker: InputFile) -> Self { + Self { bot, user_id, png_sticker } + } + + /// User identifier of sticker file owner. + pub fn user_id(mut self, val: i32) -> Self { + self.user_id = val; + self + } + + /// **Png** image with the sticker, must be up to 512 kilobytes in size, + /// dimensions must not exceed 512px, and either width or height must be + /// exactly 512px. [More info on Sending Files Âģ]. + /// + /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files + pub fn png_sticker(mut self, val: InputFile) -> Self { + self.png_sticker = val; + self + } +} diff --git a/src/requests/form_builder.rs b/src/requests/form_builder.rs new file mode 100644 index 00000000..3c7f70b9 --- /dev/null +++ b/src/requests/form_builder.rs @@ -0,0 +1,156 @@ +use std::{borrow::Cow, path::PathBuf}; + +use reqwest::multipart::Form; + +use crate::{ + requests::utils::{file_from_memory_to_part, file_to_part}, + types::{ + ChatId, InlineKeyboardMarkup, InputFile, InputMedia, MaskPosition, ParseMode, ReplyMarkup, + }, +}; + +/// This is a convenient struct that builds `reqwest::multipart::Form` +/// from scratch. +pub(crate) struct FormBuilder { + form: Form, +} + +impl FormBuilder { + pub(crate) fn new() -> Self { + Self { form: Form::new() } + } + + pub fn add_text<'a, T, N>(self, name: N, value: &T) -> Self + where + N: Into>, + T: IntoFormText, + { + match value.into_form_text() { + Some(val) => Self { form: self.form.text(name.into().into_owned(), val) }, + None => self, + } + } + + pub async fn add_input_file<'a, N>(self, name: N, value: &InputFile) -> tokio::io::Result + where + N: Into>, + { + Ok(match value { + InputFile::File(path) => self.add_file(name, path.clone()).await?, + InputFile::Memory { file_name, data } => { + self.add_file_from_memory(name, file_name.clone(), data.clone()) + } + InputFile::Url(url) => self.add_text(name, url), + InputFile::FileId(file_id) => self.add_text(name, file_id), + }) + } + + // used in SendMediaGroup + pub async fn add_file<'a, N>(self, name: N, path_to_file: PathBuf) -> tokio::io::Result + where + N: Into>, + { + Ok(Self { + form: self.form.part(name.into().into_owned(), file_to_part(path_to_file).await?), + }) + } + + fn add_file_from_memory<'a, N>( + self, + name: N, + file_name: String, + data: Cow<'static, [u8]>, + ) -> Self + where + N: Into>, + { + Self { + form: self + .form + .part(name.into().into_owned(), file_from_memory_to_part(data, file_name)), + } + } + + pub fn build(self) -> Form { + self.form + } +} + +pub(crate) trait IntoFormText { + fn into_form_text(&self) -> Option; +} + +macro_rules! impl_for_struct { + ($($name:ty),*) => { + $( + impl IntoFormText for $name { + fn into_form_text(&self) -> Option { + let json = serde_json::to_string(self) + .expect("serde_json::to_string failed"); + Some(json) + } + } + )* + }; +} + +impl_for_struct!(bool, i32, i64, u32, ReplyMarkup, InlineKeyboardMarkup, MaskPosition); + +impl IntoFormText for Option +where + T: IntoFormText, +{ + fn into_form_text(&self) -> Option { + self.as_ref().and_then(IntoFormText::into_form_text) + } +} + +// TODO: fix InputMedia implementation of IntoFormValue (for now it doesn't +// encode files :|) +impl IntoFormText for Vec { + fn into_form_text(&self) -> Option { + let json = serde_json::to_string(self).expect("serde_json::to_string failed"); + Some(json) + } +} + +impl IntoFormText for InputMedia { + fn into_form_text(&self) -> Option { + let json = serde_json::to_string(self).expect("serde_json::to_string failed"); + Some(json) + } +} + +impl IntoFormText for str { + fn into_form_text(&self) -> Option { + Some(self.to_owned()) + } +} + +impl IntoFormText for ParseMode { + fn into_form_text(&self) -> Option { + let string = match self { + ParseMode::MarkdownV2 => String::from("MarkdownV2"), + ParseMode::HTML => String::from("HTML"), + #[allow(deprecated)] + ParseMode::Markdown => String::from("Markdown"), + }; + Some(string) + } +} + +impl IntoFormText for ChatId { + fn into_form_text(&self) -> Option { + let string = match self { + ChatId::Id(id) => id.to_string(), + ChatId::ChannelUsername(username) => username.clone(), + }; + Some(string) + } +} + +impl IntoFormText for String { + fn into_form_text(&self) -> Option { + Some(self.clone()) + } +} diff --git a/src/requests/mod.rs b/src/requests/mod.rs new file mode 100644 index 00000000..09f2af6c --- /dev/null +++ b/src/requests/mod.rs @@ -0,0 +1,32 @@ +//! API requests. + +mod all; +mod form_builder; +mod utils; + +pub use all::*; + +/// A type that is returned after making a request to Telegram. +pub type ResponseResult = Result; + +/// Designates an API request. +#[async_trait::async_trait] +pub trait Request { + /// A data structure returned if success. + type Output; + + /// Asynchronously sends this request to Telegram and returns the result. + async fn send(&self) -> ResponseResult; +} + +/// Designates an API request with possibly sending file. +#[async_trait::async_trait] +pub trait RequestWithFile { + /// A data structure returned if success. + type Output; + + /// Asynchronously sends this request to Telegram and returns the result. + /// Returns `tokio::io::Result::Err` when trying to send file which does not + /// exists. + async fn send(&self) -> tokio::io::Result>; +} diff --git a/src/requests/utils.rs b/src/requests/utils.rs new file mode 100644 index 00000000..f10c9dc5 --- /dev/null +++ b/src/requests/utils.rs @@ -0,0 +1,31 @@ +use std::{borrow::Cow, path::PathBuf}; + +use bytes::{Bytes, BytesMut}; +use reqwest::{multipart::Part, Body}; +use tokio_util::codec::{Decoder, FramedRead}; + +struct FileDecoder; + +impl Decoder for FileDecoder { + type Item = Bytes; + type Error = std::io::Error; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + if src.is_empty() { + return Ok(None); + } + Ok(Some(src.split().freeze())) + } +} + +pub async fn file_to_part(path_to_file: PathBuf) -> std::io::Result { + let file_name = path_to_file.file_name().unwrap().to_string_lossy().into_owned(); + + let file = FramedRead::new(tokio::fs::File::open(path_to_file).await?, FileDecoder); + + Ok(Part::stream(Body::wrap_stream(file)).file_name(file_name)) +} + +pub fn file_from_memory_to_part(data: Cow<'static, [u8]>, name: String) -> Part { + Part::bytes(data).file_name(name) +} diff --git a/src/types/allowed_update.rs b/src/types/allowed_update.rs new file mode 100644 index 00000000..f3b920ac --- /dev/null +++ b/src/types/allowed_update.rs @@ -0,0 +1,14 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum AllowedUpdate { + Message, + EditedMessage, + ChannelPost, + EditedChannelPost, + InlineQuery, + ChosenInlineResult, + CallbackQuery, +} diff --git a/src/types/animation.rs b/src/types/animation.rs new file mode 100644 index 00000000..dc52901c --- /dev/null +++ b/src/types/animation.rs @@ -0,0 +1,165 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{MimeWrapper, PhotoSize}; + +/// This object represents an animation file (GIF or H.264/MPEG-4 AVC video +/// without sound). +/// +/// [The official docs](https://core.telegram.org/bots/api#animation). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct Animation { + /// An identifier for this file. + pub file_id: String, + + /// Unique identifier for this file, which is supposed to be the same over + /// time and for different bots. Can't be used to download or reuse the + /// file. + pub file_unique_id: String, + + /// A video width as defined by a sender. + pub width: u32, + + /// A video height as defined by a sender. + pub height: u32, + + /// A duration of the video in seconds as defined by a sender. + pub duration: u32, + + /// An animation thumbnail as defined by a sender. + pub thumb: Option, + + /// An original animation filename as defined by a sender. + pub file_name: Option, + + /// A MIME type of the file as defined by a sender. + pub mime_type: Option, + + /// A size of a file. + pub file_size: Option, +} + +impl Animation { + pub fn new( + file_id: S1, + file_unique_id: S2, + width: u32, + height: u32, + duration: u32, + ) -> Self + where + S1: Into, + S2: Into, + { + Self { + file_id: file_id.into(), + file_unique_id: file_unique_id.into(), + width, + height, + duration, + thumb: None, + file_name: None, + mime_type: None, + file_size: None, + } + } + + pub fn file_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_id = val.into(); + self + } + + pub fn file_unique_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_unique_id = val.into(); + self + } + + pub fn width(mut self, val: u32) -> Self { + self.width = val; + self + } + + pub fn height(mut self, val: u32) -> Self { + self.height = val; + self + } + + pub fn duration(mut self, val: u32) -> Self { + self.duration = val; + self + } + + pub fn thumb(mut self, val: PhotoSize) -> Self { + self.thumb = Some(val); + self + } + + pub fn file_name(mut self, val: S) -> Self + where + S: Into, + { + self.file_name = Some(val.into()); + self + } + + pub fn mime_type(mut self, val: MimeWrapper) -> Self { + self.mime_type = Some(val); + self + } + + pub fn file_size(mut self, val: u32) -> Self { + self.file_size = Some(val); + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let json = r#"{ + "file_id":"id", + "file_unique_id":"", + "width":320, + "height":320, + "duration":59, + "thumb":{ + "file_id":"id", + "file_unique_id":"", + "width":320, + "height":320, + "file_size":3452 + }, + "file_name":"some", + "mime_type":"video/gif", + "file_size":6500}"#; + let expected = Animation { + file_id: "id".to_string(), + file_unique_id: "".to_string(), + width: 320, + height: 320, + duration: 59, + thumb: Some(PhotoSize { + file_id: "id".to_string(), + file_unique_id: "".to_string(), + width: 320, + height: 320, + file_size: Some(3452), + }), + file_name: Some("some".to_string()), + mime_type: Some(MimeWrapper("video/gif".parse().unwrap())), + file_size: Some(6500), + }; + let actual = serde_json::from_str::(json).unwrap(); + assert_eq!(actual, expected) + } +} diff --git a/src/types/audio.rs b/src/types/audio.rs new file mode 100644 index 00000000..d5599c20 --- /dev/null +++ b/src/types/audio.rs @@ -0,0 +1,152 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{MimeWrapper, PhotoSize}; + +/// This object represents an audio file to be treated as music by the Telegram +/// clients. +/// +/// [The official docs](https://core.telegram.org/bots/api#audio). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct Audio { + /// An identifier for this file. + pub file_id: String, + + /// Unique identifier for this file, which is supposed to be the same over + /// time and for different bots. Can't be used to download or reuse the + /// file. + pub file_unique_id: String, + + /// A duration of the audio in seconds as defined by a sender. + pub duration: u32, + + /// A performer of the audio as defined by a sender or by audio tags. + pub performer: Option, + + /// A title of the audio as defined by sender or by audio tags. + pub title: Option, + + /// A MIME type of the file as defined by a sender. + pub mime_type: Option, + + /// A size of a file. + pub file_size: Option, + + /// A thumbnail of the album cover to which the music file belongs. + pub thumb: Option, +} + +impl Audio { + pub fn new(file_id: S1, file_unique_id: S2, duration: u32) -> Self + where + S1: Into, + S2: Into, + { + Self { + file_id: file_id.into(), + file_unique_id: file_unique_id.into(), + duration, + performer: None, + title: None, + mime_type: None, + file_size: None, + thumb: None, + } + } + + pub fn file_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_id = val.into(); + self + } + + pub fn file_unique_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_unique_id = val.into(); + self + } + + pub fn duration(mut self, val: u32) -> Self { + self.duration = val; + self + } + + pub fn performer(mut self, val: S) -> Self + where + S: Into, + { + self.performer = Some(val.into()); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = Some(val.into()); + self + } + + pub fn mime_type(mut self, val: MimeWrapper) -> Self { + self.mime_type = Some(val); + self + } + + pub fn file_size(mut self, val: u32) -> Self { + self.file_size = Some(val); + self + } + + pub fn thumb(mut self, val: PhotoSize) -> Self { + self.thumb = Some(val); + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let json = r#"{ + "file_id":"id", + "file_unique_id":"", + "duration":60, + "performer":"Performer", + "title":"Title", + "mime_type":"application/zip", + "file_size":123456, + "thumb":{ + "file_id":"id", + "file_unique_id":"", + "width":320, + "height":320, + "file_size":3452 + } + }"#; + let expected = Audio { + file_id: "id".to_string(), + file_unique_id: "".to_string(), + duration: 60, + performer: Some("Performer".to_string()), + title: Some("Title".to_string()), + mime_type: Some(serde_json::from_str("\"application/zip\"").unwrap()), + file_size: Some(123_456), + thumb: Some(PhotoSize { + file_id: "id".to_string(), + file_unique_id: "".to_string(), + width: 320, + height: 320, + file_size: Some(3452), + }), + }; + let actual = serde_json::from_str::

(mut self, val: P) -> Self + where + P: Into>, + { + self.translation = Some(val.into()); + self + } +} + +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct EncryptedPassportElementDriverLicense { + /// Base64-encoded encrypted Telegram Passport element data provided + /// by the user, available for `personal_details`, `passport`, + /// `driver_license`, `identity_card`, `internal_passport` and + /// `address` types. Can be decrypted and verified using the + /// accompanying [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub data: String, + + /// Encrypted file with the front side of the document, provided by the + /// user. Available for `passport`, `driver_license`, `identity_card` + /// and `internal_passport`. The file can be decrypted and verified + /// using the accompanying [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub front_side: PassportFile, + + /// Encrypted file with the reverse side of the document, provided by + /// the user. Available for `driver_license` and `identity_card`. The + /// file can be decrypted and verified using the accompanying + /// [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub reverse_side: PassportFile, + + /// Encrypted file with the selfie of the user holding a document, + /// provided by the user; available for `passport`, `driver_license`, + /// `identity_card` and `internal_passport`. The file can be decrypted + /// and verified using the accompanying [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub selfie: PassportFile, + + /// Array of encrypted files with translated versions of documents + /// provided by the user. Available if requested for `passport`, + /// `driver_license`, `identity_card`, `internal_passport`, + /// `utility_bill`, `bank_statement`, `rental_agreement`, + /// `passport_registration` and `temporary_registration` types. Files + /// can be decrypted and verified using the accompanying + /// [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub translation: Option>, +} + +impl EncryptedPassportElementDriverLicense { + pub fn new( + data: S, + front_side: PassportFile, + reverse_side: PassportFile, + selfie: PassportFile, + ) -> Self + where + S: Into, + { + Self { data: data.into(), front_side, reverse_side, selfie, translation: None } + } + + pub fn data(mut self, val: S) -> Self + where + S: Into, + { + self.data = val.into(); + self + } + + pub fn front_side(mut self, val: PassportFile) -> Self { + self.front_side = val; + self + } + + pub fn reverse_side(mut self, val: PassportFile) -> Self { + self.reverse_side = val; + self + } + + pub fn selfie(mut self, val: PassportFile) -> Self { + self.selfie = val; + self + } + pub fn translation

(mut self, val: P) -> Self + where + P: Into>, + { + self.translation = Some(val.into()); + self + } +} + +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct EncryptedPassportElementIdentityCard { + /// Base64-encoded encrypted Telegram Passport element data provided + /// by the user, available for `personal_details`, `passport`, + /// `driver_license`, `identity_card`, `internal_passport` and + /// `address` types. Can be decrypted and verified using the + /// accompanying [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub data: String, + + /// Encrypted file with the front side of the document, provided by the + /// user. Available for `passport`, `driver_license`, `identity_card` + /// and `internal_passport`. The file can be decrypted and verified + /// using the accompanying [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub front_side: PassportFile, + + /// Encrypted file with the reverse side of the document, provided by + /// the user. Available for `driver_license` and `identity_card`. The + /// file can be decrypted and verified using the accompanying + /// [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub reverse_side: PassportFile, + + /// Encrypted file with the selfie of the user holding a document, + /// provided by the user; available for `passport`, `driver_license`, + /// `identity_card` and `internal_passport`. The file can be decrypted + /// and verified using the accompanying [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub selfie: PassportFile, + + /// Array of encrypted files with translated versions of documents + /// provided by the user. Available if requested for `passport`, + /// `driver_license`, `identity_card`, `internal_passport`, + /// `utility_bill`, `bank_statement`, `rental_agreement`, + /// `passport_registration` and `temporary_registration` types. Files + /// can be decrypted and verified using the accompanying + /// [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub translation: Option>, +} + +impl EncryptedPassportElementIdentityCard { + pub fn new( + data: S, + front_side: PassportFile, + reverse_side: PassportFile, + selfie: PassportFile, + ) -> Self + where + S: Into, + { + Self { data: data.into(), front_side, reverse_side, selfie, translation: None } + } + + pub fn data(mut self, val: S) -> Self + where + S: Into, + { + self.data = val.into(); + self + } + + pub fn front_side(mut self, val: PassportFile) -> Self { + self.front_side = val; + self + } + + pub fn reverse_side(mut self, val: PassportFile) -> Self { + self.reverse_side = val; + self + } + + pub fn selfie(mut self, val: PassportFile) -> Self { + self.selfie = val; + self + } + pub fn translation

(mut self, val: P) -> Self + where + P: Into>, + { + self.translation = Some(val.into()); + self + } +} + +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct EncryptedPassportElementInternalPassport { + /// Base64-encoded encrypted Telegram Passport element data provided + /// by the user, available for `personal_details`, `passport`, + /// `driver_license`, `identity_card`, `internal_passport` and + /// `address` types. Can be decrypted and verified using the + /// accompanying [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub data: String, + + /// Encrypted file with the front side of the document, provided by the + /// user. Available for `passport`, `driver_license`, `identity_card` + /// and `internal_passport`. The file can be decrypted and verified + /// using the accompanying [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub front_side: PassportFile, + + /// Encrypted file with the selfie of the user holding a document, + /// provided by the user; available for `passport`, `driver_license`, + /// `identity_card` and `internal_passport`. The file can be decrypted + /// and verified using the accompanying [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub selfie: PassportFile, + + /// Array of encrypted files with translated versions of documents + /// provided by the user. Available if requested for `passport`, + /// `driver_license`, `identity_card`, `internal_passport`, + /// `utility_bill`, `bank_statement`, `rental_agreement`, + /// `passport_registration` and `temporary_registration` types. Files + /// can be decrypted and verified using the accompanying + /// [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub translation: Option>, +} + +impl EncryptedPassportElementInternalPassport { + pub fn new(data: S, front_side: PassportFile, selfie: PassportFile) -> Self + where + S: Into, + { + Self { data: data.into(), front_side, selfie, translation: None } + } + + pub fn data(mut self, val: S) -> Self + where + S: Into, + { + self.data = val.into(); + self + } + + pub fn front_side(mut self, val: PassportFile) -> Self { + self.front_side = val; + self + } + + pub fn selfie(mut self, val: PassportFile) -> Self { + self.selfie = val; + self + } + + pub fn translation

(mut self, val: P) -> Self + where + P: Into>, + { + self.translation = Some(val.into()); + self + } +} + +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct EncryptedPassportElementAddress { + /// Base64-encoded encrypted Telegram Passport element data provided + /// by the user, available for `personal_details`, `passport`, + /// `driver_license`, `identity_card`, `internal_passport` and + /// `address` types. Can be decrypted and verified using the + /// accompanying [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub data: String, +} + +impl EncryptedPassportElementAddress { + pub fn new(data: S) -> Self + where + S: Into, + { + Self { data: data.into() } + } + + pub fn data(mut self, val: S) -> Self + where + S: Into, + { + self.data = val.into(); + self + } +} + +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct EncryptedPassportElementUtilityBill { + /// Array of encrypted files with documents provided by the user, + /// available for `utility_bill`, `bank_statement`, `rental_agreement`, + /// `passport_registration` and `temporary_registration` types. Files + /// can be decrypted and verified using the accompanying + /// [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub files: Vec, + + /// Array of encrypted files with translated versions of documents + /// provided by the user. Available if requested for `passport`, + /// `driver_license`, `identity_card`, `internal_passport`, + /// `utility_bill`, `bank_statement`, `rental_agreement`, + /// `passport_registration` and `temporary_registration` types. Files + /// can be decrypted and verified using the accompanying + /// [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub translation: Option>, +} + +impl EncryptedPassportElementUtilityBill { + pub fn new(files: F) -> Self + where + F: Into>, + { + Self { files: files.into(), translation: None } + } + + pub fn files

(mut self, val: P) -> Self + where + P: Into>, + { + self.files = val.into(); + self + } + + pub fn translation

(mut self, val: P) -> Self + where + P: Into>, + { + self.translation = Some(val.into()); + self + } +} + +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct EncryptedPassportElementBankStatement { + /// Array of encrypted files with documents provided by the user, + /// available for `utility_bill`, `bank_statement`, `rental_agreement`, + /// `passport_registration` and `temporary_registration` types. Files + /// can be decrypted and verified using the accompanying + /// [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub files: Vec, + + /// Array of encrypted files with translated versions of documents + /// provided by the user. Available if requested for `passport`, + /// `driver_license`, `identity_card`, `internal_passport`, + /// `utility_bill`, `bank_statement`, `rental_agreement`, + /// `passport_registration` and `temporary_registration` types. Files + /// can be decrypted and verified using the accompanying + /// [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub translation: Option>, +} + +impl EncryptedPassportElementBankStatement { + pub fn new(files: F) -> Self + where + F: Into>, + { + Self { files: files.into(), translation: None } + } + + pub fn files

(mut self, val: P) -> Self + where + P: Into>, + { + self.files = val.into(); + self + } + + pub fn translation

(mut self, val: P) -> Self + where + P: Into>, + { + self.translation = Some(val.into()); + self + } +} + +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct EncryptedPassportElementRentalAgreement { + /// Array of encrypted files with documents provided by the user, + /// available for `utility_bill`, `bank_statement`, `rental_agreement`, + /// `passport_registration` and `temporary_registration` types. Files + /// can be decrypted and verified using the accompanying + /// [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub files: Vec, + + /// Array of encrypted files with translated versions of documents + /// provided by the user. Available if requested for `passport`, + /// `driver_license`, `identity_card`, `internal_passport`, + /// `utility_bill`, `bank_statement`, `rental_agreement`, + /// `passport_registration` and `temporary_registration` types. Files + /// can be decrypted and verified using the accompanying + /// [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub translation: Option>, +} + +impl EncryptedPassportElementRentalAgreement { + pub fn new(files: F) -> Self + where + F: Into>, + { + Self { files: files.into(), translation: None } + } + + pub fn files

(mut self, val: P) -> Self + where + P: Into>, + { + self.files = val.into(); + self + } + + pub fn translation

(mut self, val: P) -> Self + where + P: Into>, + { + self.translation = Some(val.into()); + self + } +} + +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct EncryptedPassportElementPassportRegistration { + /// Array of encrypted files with documents provided by the user, + /// available for `utility_bill`, `bank_statement`, `rental_agreement`, + /// `passport_registration` and `temporary_registration` types. Files + /// can be decrypted and verified using the accompanying + /// [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub files: Vec, + + /// Array of encrypted files with translated versions of documents + /// provided by the user. Available if requested for `passport`, + /// `driver_license`, `identity_card`, `internal_passport`, + /// `utility_bill`, `bank_statement`, `rental_agreement`, + /// `passport_registration` and `temporary_registration` types. Files + /// can be decrypted and verified using the accompanying + /// [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub translation: Option>, +} + +impl EncryptedPassportElementPassportRegistration { + pub fn new(files: F) -> Self + where + F: Into>, + { + Self { files: files.into(), translation: None } + } + + pub fn files

(mut self, val: P) -> Self + where + P: Into>, + { + self.files = val.into(); + self + } + + pub fn translation

(mut self, val: P) -> Self + where + P: Into>, + { + self.translation = Some(val.into()); + self + } +} + +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct EncryptedPassportElementTemporaryRegistration { + /// Array of encrypted files with documents provided by the user, + /// available for `utility_bill`, `bank_statement`, `rental_agreement`, + /// `passport_registration` and `temporary_registration` types. Files + /// can be decrypted and verified using the accompanying + /// [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub files: Vec, + + /// Array of encrypted files with translated versions of documents + /// provided by the user. Available if requested for `passport`, + /// `driver_license`, `identity_card`, `internal_passport`, + /// `utility_bill`, `bank_statement`, `rental_agreement`, + /// `passport_registration` and `temporary_registration` types. Files + /// can be decrypted and verified using the accompanying + /// [`EncryptedCredentials`]. + /// + /// [`EncryptedCredentials`]: + /// crate::types::EncryptedCredentials + pub translation: Option>, +} + +impl EncryptedPassportElementTemporaryRegistration { + pub fn new(files: F) -> Self + where + F: Into>, + { + Self { files: files.into(), translation: None } + } + + pub fn files

(mut self, val: P) -> Self + where + P: Into>, + { + self.files = val.into(); + self + } + + pub fn translation

(mut self, val: P) -> Self + where + P: Into>, + { + self.translation = Some(val.into()); + self + } +} + +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct EncryptedPassportElementPhoneNumber { + /// User's verified phone number, available only for `phone_number` + /// type. + pub phone_number: String, +} + +impl EncryptedPassportElementPhoneNumber { + pub fn new(phone_number: S) -> Self + where + S: Into, + { + Self { phone_number: phone_number.into() } + } + + pub fn phone_number(mut self, val: S) -> Self + where + S: Into, + { + self.phone_number = val.into(); + self + } +} + +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct EncryptedPassportElementEmail { + /// User's verified email address, available only for `email` type. + pub email: String, +} + +impl EncryptedPassportElementEmail { + pub fn new(email: S) -> Self + where + S: Into, + { + Self { email: email.into() } + } + + pub fn email(mut self, val: S) -> Self + where + S: Into, + { + self.email = val.into(); + self + } +} diff --git a/src/types/file.rs b/src/types/file.rs new file mode 100644 index 00000000..721f61c0 --- /dev/null +++ b/src/types/file.rs @@ -0,0 +1,75 @@ +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/`. +/// 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`]. +/// +/// [The official docs](https://core.telegram.org/bots/api#file). +/// +/// [`Bot::get_file`]: crate::Bot::get_file +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct File { + /// Identifier for this file. + pub file_id: String, + + /// Unique identifier for this file, which is supposed to be the same over + /// time and for different bots. Can't be used to download or reuse the + /// file. + pub file_unique_id: String, + + /// File size, if known. + pub file_size: u32, + + // TODO: chacge "Use ..." to use bot.download... + /// File path. Use `https://api.telegram.org/file/bot/` + /// to get the file. + pub file_path: String, +} + +impl File { + pub fn new(file_id: S1, file_unique_id: S2, file_size: u32, file_path: S3) -> Self + where + S1: Into, + S2: Into, + S3: Into, + { + Self { + file_id: file_id.into(), + file_unique_id: file_unique_id.into(), + file_size, + file_path: file_path.into(), + } + } + + pub fn file_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_id = val.into(); + self + } + + pub fn file_unique_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_unique_id = val.into(); + self + } + + pub fn file_size(mut self, val: u32) -> Self { + self.file_size = val; + self + } + + pub fn file_path(mut self, val: S) -> Self + where + S: Into, + { + self.file_id = val.into(); + self + } +} diff --git a/src/types/force_reply.rs b/src/types/force_reply.rs new file mode 100644 index 00000000..a6a74408 --- /dev/null +++ b/src/types/force_reply.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; + +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]. +/// +/// [The official docs](https://core.telegram.org/bots/api#forcereply). +/// +/// [privacy mode]: https://core.telegram.org/bots#privacy-mode +#[serde_with_macros::skip_serializing_none] +#[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct ForceReply { + /// Shows reply interface to the user, as if they manually selected the + /// bot‘s message and tapped ’Reply'. + pub force_reply: True, + + /// Use this parameter if you want to force reply from specific users only. + /// Targets: 1) users that are `@mentioned` in the text of the + /// [`Message`] object; 2) if the bot's message is a reply + /// (has reply_to_message_id), sender of the original message. + /// + /// [`Message`]: crate::types::Message + pub selective: Option, +} + +impl ForceReply { + pub fn new() -> Self { + Self::default() + } + + pub fn selective(mut self, val: bool) -> Self { + self.selective = Some(val); + self + } +} diff --git a/src/types/game.rs b/src/types/game.rs new file mode 100644 index 00000000..52fe01b8 --- /dev/null +++ b/src/types/game.rs @@ -0,0 +1,106 @@ +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. +/// +/// [@Botfather]: https://t.me/botfather +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct Game { + /// Title of the game. + pub title: String, + + /// Description of the game. + pub description: String, + + /// Photo that will be displayed in the game message in chats. + pub photo: Vec, + + /// Brief description of the game or high scores included in the game + /// message. Can be automatically edited to include current high scores + /// for the game when the bot calls [`Bot::set_game_score`], or manually + /// edited using [`Bot::edit_message_text`]. 0-4096 characters. + /// + /// [`Bot::set_game_score`]: crate::Bot::set_game_score + /// + /// [`Bot::edit_message_text`]: crate::Bot::edit_message_text + pub text: Option, + + /// Special entities that appear in text, such as usernames, URLs, bot + /// commands, etc. + pub text_entities: Option>, + + /// Animation that will be displayed in the game message in chats. Upload + /// via [@Botfather]. + /// + /// [@Botfather]: https://t.me/botfather + pub animation: Option, +} + +impl Game { + pub fn new(title: S1, description: S2, photo: P) -> Self + where + S1: Into, + S2: Into, + P: Into>, + { + Self { + title: title.into(), + description: description.into(), + photo: photo.into(), + text: None, + text_entities: None, + animation: None, + } + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = val.into(); + self + } + + pub fn description(mut self, val: S) -> Self + where + S: Into, + { + self.description = val.into(); + self + } + + pub fn photo

(mut self, val: P) -> Self + where + P: Into>, + { + self.photo = val.into(); + self + } + + pub fn text(mut self, val: S) -> Self + where + S: Into, + { + self.text = Some(val.into()); + self + } + + pub fn text_entities(mut self, val: T) -> Self + where + T: Into>, + { + self.text_entities = Some(val.into()); + self + } + + pub fn animation(mut self, val: Animation) -> Self { + self.animation = Some(val); + self + } +} diff --git a/src/types/game_high_score.rs b/src/types/game_high_score.rs new file mode 100644 index 00000000..f713b18e --- /dev/null +++ b/src/types/game_high_score.rs @@ -0,0 +1,40 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::user::User; + +/// This object represents one row of the high scores table for a game. +/// +/// [The official docs](https://core.telegram.org/bots/api#gamehighscore). +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct GameHighScore { + /// Position in high score table for the game. + pub position: u32, + + /// User. + pub user: User, + + /// Score. + pub score: u32, +} + +impl GameHighScore { + pub fn new(position: u32, user: User, score: u32) -> Self { + Self { position, user, score } + } + + pub fn position(mut self, val: u32) -> Self { + self.position = val; + self + } + + pub fn user(mut self, val: User) -> Self { + self.user = val; + self + } + + pub fn score(mut self, val: u32) -> Self { + self.score = val; + self + } +} diff --git a/src/types/inline_keyboard_button.rs b/src/types/inline_keyboard_button.rs new file mode 100644 index 00000000..12497463 --- /dev/null +++ b/src/types/inline_keyboard_button.rs @@ -0,0 +1,135 @@ +use crate::types::{CallbackGame, LoginUrl}; +use serde::{Deserialize, Serialize}; + +/// This object represents one button of an inline keyboard. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinekeyboardbutton). +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineKeyboardButton { + /// Label text on the button. + pub text: String, + + #[serde(flatten)] + pub kind: InlineKeyboardButtonKind, +} + +impl InlineKeyboardButton { + pub fn new(text: S, kind: InlineKeyboardButtonKind) -> Self + where + S: Into, + { + Self { text: text.into(), kind } + } + + pub fn text(mut self, val: S) -> Self + where + S: Into, + { + self.text = val.into(); + self + } + + pub fn kind(mut self, val: InlineKeyboardButtonKind) -> Self { + self.kind = val; + self + } +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum InlineKeyboardButtonKind { + /// HTTP or tg:// url to be opened when button is pressed. + Url(String), + + /// An HTTP URL used to automatically authorize the user. Can be used as a + /// replacement for the [Telegram Login Widget](). + /// + /// [Telegram Login Widget]: https://core.telegram.org/widgets/login + LoginUrl(LoginUrl), + + /// Data to be sent in a [`CallbackQuery`] to the bot when button is + /// pressed, 1-64 bytes. + /// + /// [`CallbackQuery`]: crate::types::CallbackQuery + CallbackData(String), + + /// If set, pressing the button will prompt the user to select one of their + /// chats, open that chat and insert the bot‘s username and the specified + /// inline query in the input field. Can be empty, in which case just the + /// bot’s username will be inserted. + /// + /// Note: This offers an easy way for users to start using your bot in + /// [inline mode] when they are currently in a private chat with it. + /// Especially useful when combined with [switch_pmâ€Ļ] actions – in this + /// case the user will be automatically returned to the chat they + /// switched from, skipping the chat selection screen. + /// + /// [inline mode]: https://core.telegram.org/bots/inline + /// [switch_pmâ€Ļ]: https://core.telegram.org/bots/api#answerinlinequery + SwitchInlineQuery(String), + + /// If set, pressing the button will insert the bot‘s username and the + /// specified inline query in the current chat's input field. + /// Can be empty, in which case only the bot’s username will be + /// inserted. + /// + ///This offers a quick way for the user to open your bot in inline mode in + /// the same chat – good for selecting something from multiple options. + SwitchInlineQueryCurrentChat(String), + + /// Description of the game that will be launched when the user presses the + /// button. + /// + /// ## Note + /// This type of button **must** always be the first button in the first + /// row. + CallbackGame(CallbackGame), + + /// Specify True, to send a [Pay button]. + /// + /// ## Note + /// This type of button **must** always be the first button in the first + /// row. + /// + /// [Pay button]: https://core.telegram.org/bots/api#payments + Pay(bool), +} + +/// Build buttons. +/// +/// # Examples +/// ``` +/// use teloxide::types::InlineKeyboardButton; +/// +/// let url_button = InlineKeyboardButton::url("Text".to_string(), "http://url.com".to_string()); +/// ``` +impl InlineKeyboardButton { + pub fn url(text: String, url: String) -> InlineKeyboardButton { + InlineKeyboardButton { text, kind: InlineKeyboardButtonKind::Url(url) } + } + + pub fn callback(text: String, callback_data: String) -> InlineKeyboardButton { + InlineKeyboardButton { text, kind: InlineKeyboardButtonKind::CallbackData(callback_data) } + } + + pub fn switch_inline_query(text: String, switch_inline_query: String) -> InlineKeyboardButton { + InlineKeyboardButton { + text, + kind: InlineKeyboardButtonKind::SwitchInlineQuery(switch_inline_query), + } + } + + pub fn switch_inline_query_current_chat( + text: String, + switch_inline_query_current_chat: String, + ) -> InlineKeyboardButton { + InlineKeyboardButton { + text, + kind: InlineKeyboardButtonKind::SwitchInlineQueryCurrentChat( + switch_inline_query_current_chat, + ), + } + } +} diff --git a/src/types/inline_keyboard_markup.rs b/src/types/inline_keyboard_markup.rs new file mode 100644 index 00000000..b6884a96 --- /dev/null +++ b/src/types/inline_keyboard_markup.rs @@ -0,0 +1,109 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::InlineKeyboardButton; + +/// This object represents an [inline keyboard] that appears right next to the +/// message it belongs to. +/// +/// *Note*: This will only work in Telegram versions released after 9 April, +/// 2016. Older clients will display unsupported message. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinekeyboardmarkup). +/// +/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Default)] +#[non_exhaustive] +pub struct InlineKeyboardMarkup { + /// Array of button rows, each represented by an array of + /// [`InlineKeyboardButton`] objects. + /// + /// [`InlineKeyboardButton`]: crate::types::InlineKeyboardButton + pub inline_keyboard: Vec>, +} + +/// Build `InlineKeyboardMarkup`. +/// +/// # Examples +/// ``` +/// use teloxide::types::{InlineKeyboardButton, InlineKeyboardMarkup}; +/// +/// let url_button = InlineKeyboardButton::url("text".to_string(), "http://url.com".to_string()); +/// let keyboard = InlineKeyboardMarkup::default().append_row(vec![url_button]); +/// ``` +impl InlineKeyboardMarkup { + pub fn new(inline_keyboard: I1) -> Self + where + I1: Into>, + I2: Into>, + { + Self { inline_keyboard: inline_keyboard.into().into_iter().map(Into::into).collect() } + } + + pub fn inline_keyboard(mut self, val: I1) -> Self + where + I1: Into>, + I2: Into>, + { + self.inline_keyboard = val.into().into_iter().map(Into::into).collect(); + self + } + + pub fn append_row(mut self, buttons: Vec) -> Self { + self.inline_keyboard.push(buttons); + self + } + + pub fn append_to_row(mut self, button: InlineKeyboardButton, index: usize) -> Self { + match self.inline_keyboard.get_mut(index) { + Some(buttons) => buttons.push(button), + None => self.inline_keyboard.push(vec![button]), + }; + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn append_row() { + let button1 = InlineKeyboardButton::url("text 1".to_string(), "url 1".to_string()); + let button2 = InlineKeyboardButton::url("text 2".to_string(), "url 2".to_string()); + + let markup = + InlineKeyboardMarkup::default().append_row(vec![button1.clone(), button2.clone()]); + + let expected = InlineKeyboardMarkup { inline_keyboard: vec![vec![button1, button2]] }; + + assert_eq!(markup, expected); + } + + #[test] + fn append_to_row_existent_row() { + let button1 = InlineKeyboardButton::url("text 1".to_string(), "url 1".to_string()); + let button2 = InlineKeyboardButton::url("text 2".to_string(), "url 2".to_string()); + + let markup = InlineKeyboardMarkup::default() + .append_row(vec![button1.clone()]) + .append_to_row(button2.clone(), 0); + + let expected = InlineKeyboardMarkup { inline_keyboard: vec![vec![button1, button2]] }; + + assert_eq!(markup, expected); + } + + #[test] + fn append_to_row_nonexistent_row() { + let button1 = InlineKeyboardButton::url("text 1".to_string(), "url 1".to_string()); + let button2 = InlineKeyboardButton::url("text 2".to_string(), "url 2".to_string()); + + let markup = InlineKeyboardMarkup::default() + .append_row(vec![button1.clone()]) + .append_to_row(button2.clone(), 1); + + let expected = InlineKeyboardMarkup { inline_keyboard: vec![vec![button1], vec![button2]] }; + + assert_eq!(markup, expected); + } +} diff --git a/src/types/inline_query.rs b/src/types/inline_query.rs new file mode 100644 index 00000000..a1c15fc8 --- /dev/null +++ b/src/types/inline_query.rs @@ -0,0 +1,74 @@ +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. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequery). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQuery { + /// Unique identifier for this query. + pub id: String, + + /// Sender. + pub from: User, + + /// Sender location, only for bots that request user location. + pub location: Option, + + /// Text of the query (up to 512 characters). + pub query: String, + + /// Offset of the results to be returned, can be controlled by the bot. + pub offset: String, +} + +impl InlineQuery { + pub fn new(id: S1, from: User, query: S2, offset: S3) -> Self + where + S1: Into, + S2: Into, + S3: Into, + { + Self { id: id.into(), from, location: None, query: query.into(), offset: offset.into() } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn from(mut self, val: User) -> Self { + self.from = val; + self + } + + pub fn location(mut self, val: Location) -> Self { + self.location = Some(val); + self + } + + pub fn query(mut self, val: S) -> Self + where + S: Into, + { + self.query = val.into(); + self + } + + pub fn offset(mut self, val: S) -> Self + where + S: Into, + { + self.offset = val.into(); + self + } +} diff --git a/src/types/inline_query_result.rs b/src/types/inline_query_result.rs new file mode 100644 index 00000000..cb9976de --- /dev/null +++ b/src/types/inline_query_result.rs @@ -0,0 +1,102 @@ +#![allow(clippy::large_enum_variant)] + +use derive_more::From; +use serde::{Deserialize, Serialize}; + +use crate::types::{ + InlineQueryResultArticle, InlineQueryResultAudio, InlineQueryResultCachedAudio, + InlineQueryResultCachedDocument, InlineQueryResultCachedGif, InlineQueryResultCachedMpeg4Gif, + InlineQueryResultCachedPhoto, InlineQueryResultCachedSticker, InlineQueryResultCachedVideo, + InlineQueryResultCachedVoice, InlineQueryResultContact, InlineQueryResultDocument, + InlineQueryResultGame, InlineQueryResultGif, InlineQueryResultLocation, + InlineQueryResultMpeg4Gif, InlineQueryResultPhoto, InlineQueryResultVenue, + InlineQueryResultVideo, InlineQueryResultVoice, +}; + +/// This object represents one result of an inline query. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresult). +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, From)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum InlineQueryResult { + #[serde(rename = "audio")] + CachedAudio(InlineQueryResultCachedAudio), + #[serde(rename = "document")] + CachedDocument(InlineQueryResultCachedDocument), + #[serde(rename = "gif")] + CachedGif(InlineQueryResultCachedGif), + #[serde(rename = "mpeg4_gif")] + CachedMpeg4Gif(InlineQueryResultCachedMpeg4Gif), + #[serde(rename = "photo")] + CachedPhoto(InlineQueryResultCachedPhoto), + #[serde(rename = "sticker")] + CachedSticker(InlineQueryResultCachedSticker), + #[serde(rename = "video")] + CachedVideo(InlineQueryResultCachedVideo), + #[serde(rename = "voice")] + CachedVoice(InlineQueryResultCachedVoice), + + Article(InlineQueryResultArticle), + Audio(InlineQueryResultAudio), + Contact(InlineQueryResultContact), + Game(InlineQueryResultGame), + Document(InlineQueryResultDocument), + Gif(InlineQueryResultGif), + Location(InlineQueryResultLocation), + #[serde(rename = "mpeg4_gif")] + Mpeg4Gif(InlineQueryResultMpeg4Gif), + Photo(InlineQueryResultPhoto), + Venue(InlineQueryResultVenue), + Video(InlineQueryResultVideo), + Voice(InlineQueryResultVoice), +} + +#[cfg(test)] +mod tests { + use crate::types::{ + inline_keyboard_markup::InlineKeyboardMarkup, parse_mode::ParseMode, InlineQueryResult, + InlineQueryResultCachedAudio, InputMessageContent, InputMessageContentText, + }; + + #[test] + fn cached_audio_min_serialize() { + let structure = InlineQueryResult::CachedAudio(InlineQueryResultCachedAudio { + id: String::from("id"), + audio_file_id: String::from("audio_file_id"), + caption: None, + parse_mode: None, + reply_markup: None, + input_message_content: None, + }); + + let expected_json = r#"{"type":"audio","id":"id","audio_file_id":"audio_file_id"}"#; + let actual_json = serde_json::to_string(&structure).unwrap(); + + assert_eq!(expected_json, actual_json); + } + + #[test] + fn cached_audio_full_serialize() { + let structure = InlineQueryResult::CachedAudio(InlineQueryResultCachedAudio { + id: String::from("id"), + audio_file_id: String::from("audio_file_id"), + caption: Some(String::from("caption")), + parse_mode: Some(ParseMode::HTML), + reply_markup: Some(InlineKeyboardMarkup::default()), + input_message_content: Some(InputMessageContent::Text(InputMessageContentText { + message_text: String::from("message_text"), + parse_mode: Some(ParseMode::MarkdownV2), + disable_web_page_preview: Some(true), + })), + }); + + let expected_json = r#"{"type":"audio","id":"id","audio_file_id":"audio_file_id","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let actual_json = serde_json::to_string(&structure).unwrap(); + + assert_eq!(expected_json, actual_json); + } + + // TODO: Add more tests +} diff --git a/src/types/inline_query_result_article.rs b/src/types/inline_query_result_article.rs new file mode 100644 index 00000000..2f9bd689 --- /dev/null +++ b/src/types/inline_query_result_article.rs @@ -0,0 +1,128 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{InlineKeyboardMarkup, InputMessageContent}; + +/// Represents a link to an article or web page. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultarticle). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultArticle { + /// Unique identifier for this result, 1-64 Bytes. + pub id: String, + + /// Title of the result. + pub title: String, + + /// Content of the message to be sent. + pub input_message_content: InputMessageContent, + + /// Inline keyboard attached to the message. + pub reply_markup: Option, + + /// URL of the result. + pub url: Option, + + /// Pass `true`, if you don't want the URL to be shown in the + /// message. + pub hide_url: Option, + + /// Short description of the result. + pub description: Option, + + /// Url of the thumbnail for the result. + pub thumb_url: Option, + + /// Thumbnail width. + pub thumb_width: Option, + + /// Thumbnail height. + pub thumb_height: Option, +} + +impl InlineQueryResultArticle { + pub fn new(id: S1, title: S2, input_message_content: InputMessageContent) -> Self + where + S1: Into, + S2: Into, + { + Self { + id: id.into(), + title: title.into(), + input_message_content, + reply_markup: None, + url: None, + hide_url: None, + description: None, + thumb_url: None, + thumb_width: None, + thumb_height: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = val.into(); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = val; + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn url(mut self, val: S) -> Self + where + S: Into, + { + self.url = Some(val.into()); + self + } + + pub fn hide_url(mut self, val: bool) -> Self { + self.hide_url = Some(val); + self + } + + pub fn description(mut self, val: S) -> Self + where + S: Into, + { + self.description = Some(val.into()); + self + } + + pub fn thumb_url(mut self, val: S) -> Self + where + S: Into, + { + self.thumb_url = Some(val.into()); + self + } + + pub fn thumb_width(mut self, val: i32) -> Self { + self.thumb_width = Some(val); + self + } + + pub fn thumb_height(mut self, val: i32) -> Self { + self.thumb_height = Some(val); + self + } +} diff --git a/src/types/inline_query_result_audio.rs b/src/types/inline_query_result_audio.rs new file mode 100644 index 00000000..648c1bee --- /dev/null +++ b/src/types/inline_query_result_audio.rs @@ -0,0 +1,133 @@ +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. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultaudio). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultAudio { + /// Unique identifier for this result, 1-64 bytes. + pub id: String, + + /// A valid URL for the audio file. + pub audio_url: String, + + /// Title. + pub title: String, + + /// Caption, 0-1024 characters. + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// Performer. + pub performer: Option, + + /// Audio duration in seconds. + pub audio_duration: Option, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, + + /// Content of the message to be sent instead of the audio. + pub input_message_content: Option, +} + +impl InlineQueryResultAudio { + pub fn new(id: S1, audio_url: S2, title: S3) -> Self + where + S1: Into, + S2: Into, + S3: Into, + { + Self { + id: id.into(), + audio_url: audio_url.into(), + title: title.into(), + caption: None, + parse_mode: None, + performer: None, + audio_duration: None, + reply_markup: None, + input_message_content: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn audio_url(mut self, val: S) -> Self + where + S: Into, + { + self.audio_url = val.into(); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = val.into(); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn performer(mut self, val: S) -> Self + where + S: Into, + { + self.performer = Some(val.into()); + self + } + + pub fn audio_duration(mut self, val: S) -> Self + where + S: Into, + { + self.audio_duration = Some(val.into()); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } +} diff --git a/src/types/inline_query_result_cached_audio.rs b/src/types/inline_query_result_cached_audio.rs new file mode 100644 index 00000000..5efec634 --- /dev/null +++ b/src/types/inline_query_result_cached_audio.rs @@ -0,0 +1,96 @@ +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 +/// 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#inlinequeryresultcachedaudio). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultCachedAudio { + /// Unique identifier for this result, 1-64 bytes. + pub id: String, + + /// A valid file identifier for the audio file. + pub audio_file_id: String, + + /// Caption, 0-1024 characters. + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, + + /// Content of the message to be sent instead of the audio. + pub input_message_content: Option, +} + +impl InlineQueryResultCachedAudio { + pub fn new(id: S1, audio_file_id: S2) -> Self + where + S1: Into, + S2: Into, + { + Self { + id: id.into(), + audio_file_id: audio_file_id.into(), + caption: None, + parse_mode: None, + reply_markup: None, + input_message_content: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn audio_file_id(mut self, val: S) -> Self + where + S: Into, + { + self.audio_file_id = val.into(); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } +} diff --git a/src/types/inline_query_result_cached_document.rs b/src/types/inline_query_result_cached_document.rs new file mode 100644 index 00000000..882c5858 --- /dev/null +++ b/src/types/inline_query_result_cached_document.rs @@ -0,0 +1,121 @@ +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. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcacheddocument). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultCachedDocument { + /// Unique identifier for this result, 1-64 bytes. + pub id: String, + + /// Title for the result. + pub title: String, + + /// A valid file identifier for the file. + pub document_file_id: String, + + /// Short description of the result. + pub description: Option, + + /// Caption of the document to be sent, 0-1024 characters. + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, + + /// Content of the message to be sent instead of the file. + pub input_message_content: Option, +} + +impl InlineQueryResultCachedDocument { + pub fn new(id: S1, title: S2, document_file_id: S3) -> Self + where + S1: Into, + S2: Into, + S3: Into, + { + Self { + id: id.into(), + title: title.into(), + document_file_id: document_file_id.into(), + description: None, + caption: None, + parse_mode: None, + reply_markup: None, + input_message_content: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = val.into(); + self + } + + pub fn document_file_id(mut self, val: S) -> Self + where + S: Into, + { + self.document_file_id = val.into(); + self + } + + pub fn description(mut self, val: S) -> Self + where + S: Into, + { + self.description = Some(val.into()); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } +} diff --git a/src/types/inline_query_result_cached_gif.rs b/src/types/inline_query_result_cached_gif.rs new file mode 100644 index 00000000..79fb44db --- /dev/null +++ b/src/types/inline_query_result_cached_gif.rs @@ -0,0 +1,109 @@ +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 +/// caption. Alternatively, you can use `input_message_content` to send a +/// message with specified content instead of the animation. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedgif). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultCachedGif { + /// Unique identifier for this result, 1-64 bytes. + pub id: String, + + /// A valid file identifier for the GIF file. + pub gif_file_id: String, + + /// Title for the result. + pub title: Option, + + /// Caption of the GIF file to be sent, 0-1024 characters. + pub caption: Option, + + /// Send [`ParseMode::Markdown`] or [`ParseMode::HTML`], if you want + /// Telegram apps to show [bold, italic, fixed-width text or inline + /// URLs] in the media caption. + /// + /// [`ParseMode::Markdown`]: crate::types::ParseMode::Markdown + /// [`ParseMode::HTML`]: crate::types::ParseMode::HTML + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, + + /// Content of the message to be sent instead of the GIF animation. + pub input_message_content: Option, +} + +impl InlineQueryResultCachedGif { + pub fn new(id: S1, gif_file_id: S2) -> Self + where + S1: Into, + S2: Into, + { + Self { + id: id.into(), + gif_file_id: gif_file_id.into(), + title: None, + caption: None, + parse_mode: None, + reply_markup: None, + input_message_content: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn gif_file_id(mut self, val: S) -> Self + where + S: Into, + { + self.gif_file_id = val.into(); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = Some(val.into()); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } +} diff --git a/src/types/inline_query_result_cached_mpeg4_gif.rs b/src/types/inline_query_result_cached_mpeg4_gif.rs new file mode 100644 index 00000000..8f97667d --- /dev/null +++ b/src/types/inline_query_result_cached_mpeg4_gif.rs @@ -0,0 +1,101 @@ +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. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedmpeg4gif). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultCachedMpeg4Gif { + /// Unique identifier for this result, 1-64 bytes. + pub id: String, + + /// A valid file identifier for the MP4 file. + pub mpeg4_file_id: String, + + /// Title for the result. + pub title: Option, + + /// Caption of the MPEG-4 file to be sent, 0-1024 characters. + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, + + /// Content of the message to be sent instead of the video animation. + pub input_message_content: Option, +} + +impl InlineQueryResultCachedMpeg4Gif { + pub fn new(id: S1, mpeg4_file_id: S2) -> Self + where + S1: Into, + S2: Into, + { + Self { + id: id.into(), + mpeg4_file_id: mpeg4_file_id.into(), + title: None, + caption: None, + parse_mode: None, + reply_markup: None, + input_message_content: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = Some(val.into()); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } +} diff --git a/src/types/inline_query_result_cached_photo.rs b/src/types/inline_query_result_cached_photo.rs new file mode 100644 index 00000000..ac9d28e8 --- /dev/null +++ b/src/types/inline_query_result_cached_photo.rs @@ -0,0 +1,120 @@ +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. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedphoto). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultCachedPhoto { + /// Unique identifier for this result, 1-64 bytes. + pub id: String, + + /// A valid file identifier of the photo. + pub photo_file_id: String, + + /// Title for the result. + pub title: Option, + + /// Short description of the result. + pub description: Option, + + /// Caption of the photo to be sent, 0-1024 characters. + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, + + /// Content of the message to be sent instead of the photo. + pub input_message_content: Option, +} + +impl InlineQueryResultCachedPhoto { + pub fn new(id: S1, photo_file_id: S2) -> Self + where + S1: Into, + S2: Into, + { + Self { + id: id.into(), + photo_file_id: photo_file_id.into(), + title: None, + description: None, + caption: None, + parse_mode: None, + reply_markup: None, + input_message_content: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn photo_file_id(mut self, val: S) -> Self + where + S: Into, + { + self.photo_file_id = val.into(); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = Some(val.into()); + self + } + + pub fn description(mut self, val: S) -> Self + where + S: Into, + { + self.description = Some(val.into()); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } +} diff --git a/src/types/inline_query_result_cached_sticker.rs b/src/types/inline_query_result_cached_sticker.rs new file mode 100644 index 00000000..0c4e9b88 --- /dev/null +++ b/src/types/inline_query_result_cached_sticker.rs @@ -0,0 +1,70 @@ +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. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedsticker). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultCachedSticker { + /// Unique identifier for this result, 1-64 bytes. + pub id: String, + + /// A valid file identifier of the sticker. + pub sticker_file_id: String, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, + + /// Content of the message to be sent instead of the sticker. + pub input_message_content: Option, +} + +impl InlineQueryResultCachedSticker { + pub fn new(id: S1, sticker_file_id: S2) -> Self + where + S1: Into, + S2: Into, + { + Self { + id: id.into(), + sticker_file_id: sticker_file_id.into(), + reply_markup: None, + input_message_content: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn sticker_file_id(mut self, val: S) -> Self + where + S: Into, + { + self.sticker_file_id = val.into(); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } +} diff --git a/src/types/inline_query_result_cached_video.rs b/src/types/inline_query_result_cached_video.rs new file mode 100644 index 00000000..645892b0 --- /dev/null +++ b/src/types/inline_query_result_cached_video.rs @@ -0,0 +1,121 @@ +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. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultCachedVideo { + /// Unique identifier for this result, 1-64 bytes. + pub id: String, + + /// A valid file identifier for the video file. + pub video_file_id: String, + + /// Title for each result. + pub title: String, + + /// Short description of the result. + pub description: Option, + + /// Caption of the video to be sent, 0-1024 characters. + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, + + /// Content of the message to be sent instead of the video. + pub input_message_content: Option, +} + +impl InlineQueryResultCachedVideo { + pub fn new(id: S1, video_file_id: S2, title: S3) -> Self + where + S1: Into, + S2: Into, + S3: Into, + { + Self { + id: id.into(), + video_file_id: video_file_id.into(), + title: title.into(), + description: None, + caption: None, + parse_mode: None, + reply_markup: None, + input_message_content: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn video_file_id(mut self, val: S) -> Self + where + S: Into, + { + self.video_file_id = val.into(); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = val.into(); + self + } + + pub fn description(mut self, val: S) -> Self + where + S: Into, + { + self.description = Some(val.into()); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } +} diff --git a/src/types/inline_query_result_cached_voice.rs b/src/types/inline_query_result_cached_voice.rs new file mode 100644 index 00000000..1f50672c --- /dev/null +++ b/src/types/inline_query_result_cached_voice.rs @@ -0,0 +1,109 @@ +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 +/// instead of the voice message. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultCachedVoice { + /// Unique identifier for this result, 1-64 bytes. + pub id: String, + + /// A valid file identifier for the voice message. + pub voice_file_id: String, + + /// Voice message title. + pub title: String, + + /// Caption, 0-1024 characters. + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, + + /// Content of the message to be sent instead of the voice message. + pub input_message_content: Option, +} + +impl InlineQueryResultCachedVoice { + pub fn new(id: S1, voice_file_id: S2, title: S3) -> Self + where + S1: Into, + S2: Into, + S3: Into, + { + Self { + id: id.into(), + voice_file_id: voice_file_id.into(), + title: title.into(), + caption: None, + parse_mode: None, + reply_markup: None, + input_message_content: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn voice_file_id(mut self, val: S) -> Self + where + S: Into, + { + self.voice_file_id = val.into(); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = val.into(); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } +} diff --git a/src/types/inline_query_result_contact.rs b/src/types/inline_query_result_contact.rs new file mode 100644 index 00000000..0b4ae0d8 --- /dev/null +++ b/src/types/inline_query_result_contact.rs @@ -0,0 +1,140 @@ +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. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultContact { + /// Unique identifier for this result, 1-64 Bytes. + pub id: String, + + /// Contact's phone number. + pub phone_number: String, + + /// Contact's first name. + pub first_name: String, + + /// Contact's last name. + pub last_name: Option, + + /// Additional data about the contact in the form of a [vCard], 0-2048 + /// bytes. + /// + /// [VCard]: https://en.wikipedia.org/wiki/VCard + pub vcard: Option, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, + + /// Content of the message to be sent instead of the contact. + pub input_message_content: Option, + + /// Url of the thumbnail for the result. + pub thumb_url: Option, + + /// Thumbnail width. + pub thumb_width: Option, + + /// Thumbnail height. + pub thumb_height: Option, +} + +impl InlineQueryResultContact { + pub fn new(id: S1, phone_number: S2, first_name: S3) -> Self + where + S1: Into, + S2: Into, + S3: Into, + { + Self { + id: id.into(), + phone_number: phone_number.into(), + first_name: first_name.into(), + last_name: None, + vcard: None, + reply_markup: None, + input_message_content: None, + thumb_url: None, + thumb_width: None, + thumb_height: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn phone_number(mut self, val: S) -> Self + where + S: Into, + { + self.phone_number = val.into(); + self + } + + pub fn first_name(mut self, val: S) -> Self + where + S: Into, + { + self.first_name = val.into(); + self + } + + pub fn last_name(mut self, val: S) -> Self + where + S: Into, + { + self.last_name = Some(val.into()); + self + } + + pub fn vcard(mut self, val: S) -> Self + where + S: Into, + { + self.vcard = Some(val.into()); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } + + pub fn thumb_url(mut self, val: S) -> Self + where + S: Into, + { + self.thumb_url = Some(val.into()); + self + } + + pub fn thumb_width(mut self, val: i32) -> Self { + self.thumb_width = Some(val); + self + } + + pub fn thumb_height(mut self, val: i32) -> Self { + self.thumb_height = Some(val); + self + } +} diff --git a/src/types/inline_query_result_document.rs b/src/types/inline_query_result_document.rs new file mode 100644 index 00000000..ea66f561 --- /dev/null +++ b/src/types/inline_query_result_document.rs @@ -0,0 +1,138 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{InlineKeyboardMarkup, InputMessageContent, MimeWrapper, 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. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultdocument). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultDocument { + /// Unique identifier for this result, 1-64 bytes. + pub id: String, + + /// Title for the result. + pub title: String, + + /// Caption of the document to be sent, 0-1024 characters. + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// A valid URL for the file. + pub document_url: String, + + /// Mime type of the content of the file, either `application/pdf` or + /// `application/zip`. + pub mime_type: MimeWrapper, + + /// Short description of the result. + pub description: Option, + + /// Inline keyboard attached to the message. + pub reply_markup: Option, + + /// Content of the message to be sent instead of the file. + pub input_message_content: Option, + + /// URL of the thumbnail (jpeg only) for the file. + pub thumb_url: Option, + + /// Thumbnail width. + pub thumb_width: Option, + + /// Thumbnail height. + pub thumb_height: Option, +} + +impl InlineQueryResultDocument { + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = val.into(); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn document_url(mut self, val: S) -> Self + where + S: Into, + { + self.document_url = val.into(); + self + } + + pub fn mime_type(mut self, val: MimeWrapper) -> Self { + self.mime_type = val; + self + } + + pub fn description(mut self, val: S) -> Self + where + S: Into, + { + self.description = Some(val.into()); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } + + pub fn thumb_url(mut self, val: S) -> Self + where + S: Into, + { + self.thumb_url = Some(val.into()); + self + } + + pub fn thumb_width(mut self, val: i32) -> Self { + self.thumb_width = Some(val); + self + } + + pub fn thumb_height(mut self, val: i32) -> Self { + self.thumb_height = Some(val); + self + } +} diff --git a/src/types/inline_query_result_game.rs b/src/types/inline_query_result_game.rs new file mode 100644 index 00000000..c795547c --- /dev/null +++ b/src/types/inline_query_result_game.rs @@ -0,0 +1,55 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::InlineKeyboardMarkup; + +/// Represents a [game]. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultgame). +/// +/// [game]: https://core.telegram.org/bots/api#games +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultGame { + /// Unique identifier for this result, 1-64 bytes. + pub id: String, + + /// Short name of the game. + pub game_short_name: String, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, +} + +impl InlineQueryResultGame { + pub fn new(id: S1, game_short_name: S2) -> Self + where + S1: Into, + S2: Into, + { + Self { id: id.into(), game_short_name: game_short_name.into(), reply_markup: None } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn game_short_name(mut self, val: S) -> Self + where + S: Into, + { + self.game_short_name = val.into(); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/types/inline_query_result_gif.rs b/src/types/inline_query_result_gif.rs new file mode 100644 index 00000000..71145de3 --- /dev/null +++ b/src/types/inline_query_result_gif.rs @@ -0,0 +1,148 @@ +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. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultgif). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultGif { + /// Unique identifier for this result, 1-64 bytes. + pub id: String, + + /// A valid URL for the GIF file. File size must not exceed 1MB. + pub gif_url: String, + + /// Width of the GIF. + pub gif_width: Option, + + /// Height of the GIFv. + pub gif_height: Option, + + /// Duration of the GIF. + pub gif_duration: Option, + + /// URL of the static thumbnail for the result (jpeg or gif). + pub thumb_url: String, + + /// Title for the result. + pub title: Option, + + /// Caption of the GIF file to be sent, 0-1024 characters. + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, + + /// Content of the message to be sent instead of the GIF animation. + pub input_message_content: Option, +} + +impl InlineQueryResultGif { + pub fn new(id: S1, gif_url: S2, thumb_url: S3) -> Self + where + S1: Into, + S2: Into, + S3: Into, + { + Self { + id: id.into(), + gif_url: gif_url.into(), + gif_width: None, + gif_height: None, + gif_duration: None, + thumb_url: thumb_url.into(), + title: None, + caption: None, + parse_mode: None, + reply_markup: None, + input_message_content: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn gif_url(mut self, val: S) -> Self + where + S: Into, + { + self.gif_url = val.into(); + self + } + + pub fn gif_width(mut self, val: i32) -> Self { + self.gif_width = Some(val); + self + } + + pub fn gif_height(mut self, val: i32) -> Self { + self.gif_height = Some(val); + self + } + + pub fn gif_duration(mut self, val: i32) -> Self { + self.gif_duration = Some(val); + self + } + + pub fn thumb_url(mut self, val: S) -> Self + where + S: Into, + { + self.thumb_url = val.into(); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = Some(val.into()); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } +} diff --git a/src/types/inline_query_result_location.rs b/src/types/inline_query_result_location.rs new file mode 100644 index 00000000..1d6d9270 --- /dev/null +++ b/src/types/inline_query_result_location.rs @@ -0,0 +1,128 @@ +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. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultlocation). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultLocation { + /// Unique identifier for this result, 1-64 Bytes. + pub id: String, + + /// Location latitude in degrees. + pub latitude: f64, + + /// Location longitude in degrees. + pub longitude: f64, + + /// Location title. + pub title: String, + + /// Period in seconds for which the location can be updated, should be + /// between 60 and 86400. + pub live_period: Option, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, + + /// Content of the message to be sent instead of the location. + pub input_message_content: Option, + + /// Url of the thumbnail for the result. + pub thumb_url: Option, + + /// Thumbnail width. + pub thumb_width: Option, + + /// Thumbnail height. + pub thumb_height: Option, +} + +impl InlineQueryResultLocation { + pub fn new(id: S1, title: S2, latitude: f64, longitude: f64) -> Self + where + S1: Into, + S2: Into, + { + Self { + id: id.into(), + title: title.into(), + latitude, + longitude, + live_period: None, + reply_markup: None, + input_message_content: None, + thumb_url: None, + thumb_width: None, + thumb_height: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn latitude(mut self, val: f64) -> Self { + self.latitude = val; + self + } + + pub fn longitude(mut self, val: f64) -> Self { + self.longitude = val; + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = val.into(); + self + } + + pub fn live_period(mut self, val: i32) -> Self { + self.live_period = Some(val); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } + + pub fn thumb_url(mut self, val: S) -> Self + where + S: Into, + { + self.thumb_url = Some(val.into()); + self + } + + pub fn thumb_width(mut self, val: i32) -> Self { + self.thumb_width = Some(val); + self + } + + pub fn thumb_height(mut self, val: i32) -> Self { + self.thumb_height = Some(val); + self + } +} diff --git a/src/types/inline_query_result_mpeg4_gif.rs b/src/types/inline_query_result_mpeg4_gif.rs new file mode 100644 index 00000000..6c01ceb7 --- /dev/null +++ b/src/types/inline_query_result_mpeg4_gif.rs @@ -0,0 +1,149 @@ +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 +/// a message with the specified content instead of the animation. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultmpeg4gif). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultMpeg4Gif { + /// Unique identifier for this result, 1-64 bytes. + pub id: String, + + /// A valid URL for the MP4 file. File size must not exceed 1MB. + pub mpeg4_url: String, + + /// Video width. + pub mpeg4_width: Option, + + /// Video height. + pub mpeg4_height: Option, + + /// Video duration. + pub mpeg4_duration: Option, + + /// URL of the static thumbnail (jpeg or gif) for the result. + pub thumb_url: String, + + /// Title for the result. + pub title: Option, + + /// Caption of the MPEG-4 file to be sent, 0-1024 characters. + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, + + /// Content of the message to be sent instead of the video animation. + pub input_message_content: Option, +} + +impl InlineQueryResultMpeg4Gif { + pub fn new(id: S1, mpeg4_url: S2, thumb_url: S3) -> Self + where + S1: Into, + S2: Into, + S3: Into, + { + Self { + id: id.into(), + mpeg4_url: mpeg4_url.into(), + thumb_url: thumb_url.into(), + mpeg4_width: None, + mpeg4_height: None, + mpeg4_duration: None, + title: None, + caption: None, + parse_mode: None, + reply_markup: None, + input_message_content: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn mpeg4_url(mut self, val: S) -> Self + where + S: Into, + { + self.mpeg4_url = val.into(); + self + } + + pub fn mpeg4_width(mut self, val: i32) -> Self { + self.mpeg4_width = Some(val); + self + } + + pub fn mpeg4_height(mut self, val: i32) -> Self { + self.mpeg4_height = Some(val); + self + } + + pub fn mpeg4_duration(mut self, val: i32) -> Self { + self.mpeg4_duration = Some(val); + self + } + + pub fn thumb_url(mut self, val: S) -> Self + where + S: Into, + { + self.thumb_url = val.into(); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = Some(val.into()); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } +} diff --git a/src/types/inline_query_result_photo.rs b/src/types/inline_query_result_photo.rs new file mode 100644 index 00000000..e1d32dc9 --- /dev/null +++ b/src/types/inline_query_result_photo.rs @@ -0,0 +1,152 @@ +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. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultphoto). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultPhoto { + /// Unique identifier for this result, 1-64 bytes. + pub id: String, + + /// A valid URL of the photo. Photo must be in **jpeg** format. Photo size + /// must not exceed 5MB. + pub photo_url: String, + + /// URL of the thumbnail for the photo. + pub thumb_url: String, + + /// Width of the photo. + pub photo_width: Option, + + /// Height of the photo. + pub photo_height: Option, + + /// Title for the result. + pub title: Option, + + /// Short description of the result. + pub description: Option, + + /// Caption of the photo to be sent, 0-1024 characters. + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, + + /// Content of the message to be sent instead of the photo. + pub input_message_content: Option, +} + +impl InlineQueryResultPhoto { + pub fn new(id: S1, photo_url: S2, thumb_url: S3) -> Self + where + S1: Into, + S2: Into, + S3: Into, + { + Self { + id: id.into(), + photo_url: photo_url.into(), + thumb_url: thumb_url.into(), + photo_width: None, + photo_height: None, + title: None, + description: None, + caption: None, + parse_mode: None, + reply_markup: None, + input_message_content: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn photo_url(mut self, val: S) -> Self + where + S: Into, + { + self.photo_url = val.into(); + self + } + + pub fn thumb_url(mut self, val: S) -> Self + where + S: Into, + { + self.thumb_url = val.into(); + self + } + + pub fn photo_width(mut self, val: i32) -> Self { + self.photo_width = Some(val); + self + } + + pub fn photo_height(mut self, val: i32) -> Self { + self.photo_height = Some(val); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = Some(val.into()); + self + } + + pub fn description(mut self, val: S) -> Self + where + S: Into, + { + self.description = Some(val.into()); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } +} diff --git a/src/types/inline_query_result_venue.rs b/src/types/inline_query_result_venue.rs new file mode 100644 index 00000000..c7e67443 --- /dev/null +++ b/src/types/inline_query_result_venue.rs @@ -0,0 +1,157 @@ +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. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultvenue). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultVenue { + /// Unique identifier for this result, 1-64 Bytes. + pub id: String, + + /// Latitude of the venue location in degrees. + pub latitude: f64, + + /// Longitude of the venue location in degrees. + pub longitude: f64, + + /// Title of the venue. + pub title: String, + + /// Address of the venue. + pub address: String, + + /// Foursquare identifier of the venue if known. + pub foursquare_id: Option, + + /// Foursquare type of the venue, if known. (For example, + /// `arts_entertainment/default`, `arts_entertainment/aquarium` or + /// `food/icecream`.) + pub foursquare_type: Option, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, + + /// Content of the message to be sent instead of the venue. + pub input_message_content: Option, + + /// Url of the thumbnail for the result. + pub thumb_url: Option, + + /// Thumbnail width. + pub thumb_width: Option, + + /// Thumbnail height. + pub thumb_height: Option, +} + +impl InlineQueryResultVenue { + pub fn new(id: S1, latitude: f64, longitude: f64, title: S2, address: S3) -> Self + where + S1: Into, + S2: Into, + S3: Into, + { + Self { + id: id.into(), + latitude, + longitude, + title: title.into(), + address: address.into(), + foursquare_id: None, + foursquare_type: None, + reply_markup: None, + input_message_content: None, + thumb_url: None, + thumb_width: None, + thumb_height: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn latitude(mut self, val: f64) -> Self { + self.latitude = val; + self + } + + pub fn longitude(mut self, val: f64) -> Self { + self.longitude = val; + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = val.into(); + self + } + + pub fn address(mut self, val: S) -> Self + where + S: Into, + { + self.address = val.into(); + self + } + + pub fn foursquare_id(mut self, val: S) -> Self + where + S: Into, + { + self.foursquare_id = Some(val.into()); + self + } + + pub fn foursquare_type(mut self, val: S) -> Self + where + S: Into, + { + self.foursquare_type = Some(val.into()); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } + + pub fn thumb_url(mut self, val: S) -> Self + where + S: Into, + { + self.thumb_url = Some(val.into()); + self + } + + pub fn thumb_width(mut self, val: i32) -> Self { + self.thumb_width = Some(val); + self + } + + pub fn thumb_height(mut self, val: i32) -> Self { + self.thumb_height = Some(val); + self + } +} diff --git a/src/types/inline_query_result_video.rs b/src/types/inline_query_result_video.rs new file mode 100644 index 00000000..d51258fc --- /dev/null +++ b/src/types/inline_query_result_video.rs @@ -0,0 +1,182 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{InlineKeyboardMarkup, InputMessageContent, MimeWrapper, 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 +/// caption. Alternatively, you can use `input_messaage_content` to send a +/// message with the specified content instead of the video. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultvideo). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultVideo { + /// Unique identifier for this result, 1-64 bytes. + pub id: String, + + /// A valid URL for the embedded video player or video file. + pub video_url: String, + + /// Mime type of the content of video url, `text/html` or `video/mp4`. + pub mime_type: MimeWrapper, + + /// URL of the thumbnail (jpeg only) for the video. + pub thumb_url: String, + + /// Title for the result. + pub title: String, + + /// Caption of the video to be sent, 0-1024 characters. + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// Video width. + pub video_width: Option, + + /// Video height. + pub video_height: Option, + + /// Video duration in seconds. + pub video_duration: Option, + + /// Short description of the result. + pub description: Option, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, + + /// Content of the message to be sent instead of the video. This field is + /// **required** if [`InlineQueryResultVideo`] is used to send an HTML-page + /// as a result (e.g., a YouTube video). + /// + /// [`InlineQueryResultVideo`]: + /// crate::types::InlineQueryResultVideo + pub input_message_content: Option, +} + +impl InlineQueryResultVideo { + pub fn new( + id: S1, + video_url: S2, + mime_type: MimeWrapper, + thumb_url: S3, + title: S4, + ) -> Self + where + S1: Into, + S2: Into, + S3: Into, + S4: Into, + { + Self { + id: id.into(), + video_url: video_url.into(), + mime_type, + thumb_url: thumb_url.into(), + title: title.into(), + caption: None, + parse_mode: None, + video_width: None, + video_height: None, + video_duration: None, + description: None, + reply_markup: None, + input_message_content: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn video_url(mut self, val: S) -> Self + where + S: Into, + { + self.video_url = val.into(); + self + } + + pub fn mime_type(mut self, val: MimeWrapper) -> Self { + self.mime_type = val; + self + } + + pub fn thumb_url(mut self, val: S) -> Self + where + S: Into, + { + self.thumb_url = val.into(); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = val.into(); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn video_width(mut self, val: i32) -> Self { + self.video_width = Some(val); + self + } + + pub fn video_height(mut self, val: i32) -> Self { + self.video_height = Some(val); + self + } + + pub fn video_duration(mut self, val: i32) -> Self { + self.video_duration = Some(val); + self + } + + pub fn description(mut self, val: S) -> Self + where + S: Into, + { + self.description = Some(val.into()); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } +} diff --git a/src/types/inline_query_result_voice.rs b/src/types/inline_query_result_voice.rs new file mode 100644 index 00000000..db812b7e --- /dev/null +++ b/src/types/inline_query_result_voice.rs @@ -0,0 +1,119 @@ +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. +/// +/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultvoice). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InlineQueryResultVoice { + /// Unique identifier for this result, 1-64 bytes. + pub id: String, + + /// A valid URL for the voice recording. + pub voice_url: String, + + /// Recording title. + pub title: String, + + /// Caption, 0-1024 characters. + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// Recording duration in seconds. + pub voice_duration: Option, + + /// [Inline keyboard] attached to the message. + /// + /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: Option, + + /// Content of the message to be sent instead of the voice recording. + pub input_message_content: Option, +} + +impl InlineQueryResultVoice { + pub fn new(id: S1, voice_url: S2, title: S3) -> Self + where + S1: Into, + S2: Into, + S3: Into, + { + Self { + id: id.into(), + voice_url: voice_url.into(), + title: title.into(), + caption: None, + parse_mode: None, + voice_duration: None, + reply_markup: None, + input_message_content: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn voice_url(mut self, val: S) -> Self + where + S: Into, + { + self.voice_url = val.into(); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = val.into(); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn voice_duration(mut self, value: i32) -> Self { + self.voice_duration = Some(value); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } + + pub fn input_message_content(mut self, val: InputMessageContent) -> Self { + self.input_message_content = Some(val); + self + } +} diff --git a/src/types/input_file.rs b/src/types/input_file.rs new file mode 100644 index 00000000..55e15e14 --- /dev/null +++ b/src/types/input_file.rs @@ -0,0 +1,101 @@ +use serde::{Deserialize, Serialize}; + +use std::{borrow::Cow, path::PathBuf}; + +/// This object represents the contents of a file to be uploaded. +/// +/// [The official docs](https://core.telegram.org/bots/api#inputfile). +#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize)] +#[non_exhaustive] +pub enum InputFile { + File(PathBuf), + Memory { file_name: String, data: Cow<'static, [u8]> }, + Url(String), + FileId(String), +} + +impl InputFile { + pub fn file

(path: P) -> Self + where + P: Into, + { + Self::File(path.into()) + } + + pub fn memory(file_name: S, data: D) -> Self + where + S: Into, + D: Into>, + { + Self::Memory { file_name: file_name.into(), data: data.into() } + } + + pub fn url(url: T) -> Self + where + T: Into, + { + Self::Url(url.into()) + } + + pub fn file_id(file_id: T) -> Self + where + T: Into, + { + Self::FileId(file_id.into()) + } + + pub fn as_file(&self) -> Option<&PathBuf> { + match self { + Self::File(path) => Some(path), + _ => None, + } + } + + pub fn as_url(&self) -> Option<&String> { + match self { + Self::Url(url) => Some(url), + _ => None, + } + } + + pub fn as_file_id(&self) -> Option<&String> { + match self { + Self::FileId(id) => Some(id), + _ => None, + } + } +} + +impl From for Option { + fn from(file: InputFile) -> Self { + match file { + InputFile::File(path) => Some(path), + _ => None, + } + } +} + +impl Serialize for InputFile { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + InputFile::File(path) => { + // NOTE: file should be actually attached with + // multipart/form-data + serializer.serialize_str( + // TODO: remove unwrap (?) + &format!("attach://{}", path.file_name().unwrap().to_string_lossy()), + ) + } + InputFile::Memory { data, .. } => { + // NOTE: file should be actually attached with + // multipart/form-data + serializer.serialize_str(&format!("attach://{}", String::from_utf8_lossy(data))) + } + InputFile::Url(url) => serializer.serialize_str(url), + InputFile::FileId(id) => serializer.serialize_str(id), + } + } +} diff --git a/src/types/input_media.rs b/src/types/input_media.rs new file mode 100644 index 00000000..a37ebda4 --- /dev/null +++ b/src/types/input_media.rs @@ -0,0 +1,511 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{InputFile, ParseMode}; + +/// This object represents the content of a media message to be sent. +/// +/// [The official docs](https://core.telegram.org/bots/api#inputmedia). +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum InputMedia { + Photo(InputMediaPhoto), + Video(InputMediaVideo), + Animation(InputMediaAnimation), + Audio(InputMediaAudio), + Document(InputMediaDocument), +} + +/// Represents a photo to be sent. +/// +/// [The official docs](https://core.telegram.org/bots/api#inputmediaphoto). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InputMediaPhoto { + /// File to send. + pub media: InputFile, + + /// Caption of the photo to be sent, 0-1024 characters. + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, +} + +impl InputMediaPhoto { + pub fn new(media: InputFile) -> Self { + Self { media, caption: None, parse_mode: None } + } + + pub fn media(mut self, val: InputFile) -> Self { + self.media = val; + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } +} + +/// Represents a video to be sent. +/// +/// [The official docs](https://core.telegram.org/bots/api#inputmediavideo). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InputMediaVideo { + // File to send. + pub media: InputFile, + + /// Thumbnail of the file sent; can be ignored if thumbnail generation + /// for the file is supported server-side. The thumbnail should be in + /// JPEG format and less than 200 kB in size. A thumbnail‘s width and + /// height should not exceed 320. Ignored if the file is not uploaded + /// using multipart/form-data. + pub thumb: Option, + + /// Caption of the video to be sent, 0-1024 characters. + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// Video width. + pub width: Option, + + /// Video height. + pub height: Option, + + /// Video duration. + pub duration: Option, + + /// Pass `true`, if the uploaded video is suitable for streaming. + pub supports_streaming: Option, +} + +impl InputMediaVideo { + pub fn new(media: InputFile) -> Self { + Self { + media, + thumb: None, + caption: None, + parse_mode: None, + width: None, + height: None, + duration: None, + supports_streaming: None, + } + } + + pub fn media(mut self, val: InputFile) -> Self { + self.media = val; + self + } + + pub fn thumb(mut self, val: InputFile) -> Self { + self.thumb = Some(val); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn width(mut self, val: u16) -> Self { + self.width = Some(val); + self + } + + pub fn height(mut self, val: u16) -> Self { + self.height = Some(val); + self + } + + pub fn duration(mut self, val: u16) -> Self { + self.duration = Some(val); + self + } + + pub fn supports_streaming(mut self, val: bool) -> Self { + self.supports_streaming = Some(val); + self + } +} + +/// Represents an animation file (GIF or H.264/MPEG-4 AVC video without +/// sound) to be sent. +/// +/// [The official docs](https://core.telegram.org/bots/api#inputmediaanimation). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InputMediaAnimation { + /// File to send. + pub media: InputFile, + + /// Thumbnail of the file sent; can be ignored if thumbnail generation + /// for the file is supported server-side. The thumbnail should be in + /// JPEG format and less than 200 kB in size. A thumbnail‘s width and + /// height should not exceed 320. Ignored if the file is not uploaded + /// using multipart/form-data. + pub thumb: Option, + + /// Caption of the animation to be sent, 0-1024 characters. + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// Animation width. + pub width: Option, + + /// Animation height. + pub height: Option, + + /// Animation duration. + pub duration: Option, +} + +impl InputMediaAnimation { + pub fn new(media: InputFile) -> Self { + Self { + media, + thumb: None, + caption: None, + parse_mode: None, + width: None, + height: None, + duration: None, + } + } + + pub fn media(mut self, val: InputFile) -> Self { + self.media = val; + self + } + + pub fn thumb(mut self, val: InputFile) -> Self { + self.thumb = Some(val); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn width(mut self, val: u16) -> Self { + self.width = Some(val); + self + } + + pub fn height(mut self, val: u16) -> Self { + self.height = Some(val); + self + } + + pub fn duration(mut self, val: u16) -> Self { + self.duration = Some(val); + self + } +} + +/// Represents an audio file to be treated as music to be sent. +/// +/// [The official docs](https://core.telegram.org/bots/api#inputmediaaudio). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InputMediaAudio { + /// File to send. + pub media: InputFile, + + /// Thumbnail of the file sent; can be ignored if thumbnail generation + /// for the file is supported server-side. The thumbnail should be in + /// JPEG format and less than 200 kB in size. A thumbnail‘s width and + /// height should not exceed 320. Ignored if the file is not uploaded + /// using multipart/form-data. + pub thumb: Option, + + /// Caption of the audio to be sent, 0-1024 characters. + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// Duration of the audio in seconds. + pub duration: Option, + + /// Performer of the audio. + pub performer: Option, + + /// Title of the audio. + pub title: Option, +} + +impl InputMediaAudio { + pub fn new(media: InputFile) -> Self { + Self { + media, + thumb: None, + caption: None, + parse_mode: None, + performer: None, + title: None, + duration: None, + } + } + + pub fn media(mut self, val: InputFile) -> Self { + self.media = val; + self + } + + pub fn thumb(mut self, val: InputFile) -> Self { + self.thumb = Some(val); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn duration(mut self, val: u16) -> Self { + self.duration = Some(val); + self + } + + pub fn performer(mut self, val: S) -> Self + where + S: Into, + { + self.performer = Some(val.into()); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = Some(val.into()); + self + } +} + +/// Represents a general file to be sent. +/// +/// [The official docs](https://core.telegram.org/bots/api#inputmediadocument). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InputMediaDocument { + /// File to send. + pub media: InputFile, + + /// Thumbnail of the file sent; can be ignored if thumbnail generation + /// for the file is supported server-side. The thumbnail should be in + /// JPEG format and less than 200 kB in size. A thumbnail‘s width and + /// height should not exceed 320. Ignored if the file is not uploaded + /// using multipart/form-data. + pub thumb: Option, + + /// Caption of the document to be sent, 0-1024 charactersŅŽ + pub caption: Option, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, +} + +impl InputMediaDocument { + pub fn new(media: InputFile) -> Self { + Self { media, thumb: None, caption: None, parse_mode: None } + } + + pub fn thumb(mut self, val: InputFile) -> Self { + self.thumb = Some(val); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } +} + +impl InputMedia { + pub fn media(&self) -> &InputFile { + match self { + InputMedia::Photo(InputMediaPhoto { media, .. }) + | InputMedia::Document(InputMediaDocument { media, .. }) + | InputMedia::Audio(InputMediaAudio { media, .. }) + | InputMedia::Animation(InputMediaAnimation { media, .. }) + | InputMedia::Video(InputMediaVideo { media, .. }) => media, + } + } +} + +impl From for InputFile { + fn from(media: InputMedia) -> InputFile { + match media { + InputMedia::Photo(InputMediaPhoto { media, .. }) + | InputMedia::Document(InputMediaDocument { media, .. }) + | InputMedia::Audio(InputMediaAudio { media, .. }) + | InputMedia::Animation(InputMediaAnimation { media, .. }) + | InputMedia::Video(InputMediaVideo { media, .. }) => media, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn photo_serialize() { + let expected_json = r#"{"type":"photo","media":"123456"}"#; + let photo = InputMedia::Photo(InputMediaPhoto { + media: InputFile::FileId(String::from("123456")), + caption: None, + parse_mode: None, + }); + + let actual_json = serde_json::to_string(&photo).unwrap(); + assert_eq!(expected_json, actual_json); + } + + #[test] + fn video_serialize() { + let expected_json = r#"{"type":"video","media":"123456"}"#; + let video = InputMedia::Video(InputMediaVideo { + media: InputFile::FileId(String::from("123456")), + thumb: None, + caption: None, + parse_mode: None, + width: None, + height: None, + duration: None, + supports_streaming: None, + }); + + let actual_json = serde_json::to_string(&video).unwrap(); + assert_eq!(expected_json, actual_json); + } + + #[test] + fn animation_serialize() { + let expected_json = r#"{"type":"animation","media":"123456"}"#; + let video = InputMedia::Animation(InputMediaAnimation { + media: InputFile::FileId(String::from("123456")), + thumb: None, + caption: None, + parse_mode: None, + width: None, + height: None, + duration: None, + }); + + let actual_json = serde_json::to_string(&video).unwrap(); + assert_eq!(expected_json, actual_json); + } + + #[test] + fn audio_serialize() { + let expected_json = r#"{"type":"audio","media":"123456"}"#; + let video = InputMedia::Audio(InputMediaAudio { + media: InputFile::FileId(String::from("123456")), + thumb: None, + caption: None, + parse_mode: None, + duration: None, + performer: None, + title: None, + }); + + let actual_json = serde_json::to_string(&video).unwrap(); + assert_eq!(expected_json, actual_json); + } + + #[test] + fn document_serialize() { + let expected_json = r#"{"type":"document","media":"123456"}"#; + let video = InputMedia::Document(InputMediaDocument { + media: InputFile::FileId(String::from("123456")), + thumb: None, + caption: None, + parse_mode: None, + }); + + let actual_json = serde_json::to_string(&video).unwrap(); + assert_eq!(expected_json, actual_json); + } +} diff --git a/src/types/input_message_content.rs b/src/types/input_message_content.rs new file mode 100644 index 00000000..caf57650 --- /dev/null +++ b/src/types/input_message_content.rs @@ -0,0 +1,318 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::ParseMode; + +/// This object represents the content of a message to be sent as a result of an +/// inline query. +/// +/// [The official docs](https://core.telegram.org/bots/api#inputmessagecontent). +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +#[non_exhaustive] +pub enum InputMessageContent { + Text(InputMessageContentText), + Location(InputMessageContentLocation), + Venue(InputMessageContentVenue), + Contact(InputMessageContentContact), +} +/// Represents the content of a text message to be sent as the result of an +/// inline query. +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InputMessageContentText { + /// Text of the message to be sent, 1-4096 characters. + pub message_text: String, + + /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, + /// italic, fixed-width text or inline URLs] in the media caption. + /// + /// [Markdown]: https://core.telegram.org/bots/api#markdown-style + /// [HTML]: https://core.telegram.org/bots/api#html-style + /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: Option, + + /// Disables link previews for links in the sent message. + pub disable_web_page_preview: Option, +} + +impl InputMessageContentText { + pub fn new(message_text: S) -> Self + where + S: Into, + { + Self { message_text: message_text.into(), parse_mode: None, disable_web_page_preview: None } + } + + pub fn message_text(mut self, val: S) -> Self + where + S: Into, + { + self.message_text = val.into(); + self + } + + pub fn parse_mode(mut self, val: ParseMode) -> Self { + self.parse_mode = Some(val); + self + } + + pub fn disable_web_page_preview(mut self, val: bool) -> Self { + self.disable_web_page_preview = Some(val); + self + } +} + +/// Represents the content of a location message to be sent as the result of an +/// inline query. +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InputMessageContentLocation { + /// Latitude of the location in degrees. + pub latitude: f64, + + /// Longitude of the location in degrees. + pub longitude: f64, + + /// Period in seconds for which the location can be updated, should be + /// between 60 and 86400. + pub live_period: Option, +} + +impl InputMessageContentLocation { + pub fn new(latitude: f64, longitude: f64) -> Self { + Self { latitude, longitude, live_period: None } + } + + pub fn latitude(mut self, val: f64) -> Self { + self.latitude = val; + self + } + + pub fn longitude(mut self, val: f64) -> Self { + self.longitude = val; + self + } + + pub fn live_period(mut self, val: u32) -> Self { + self.live_period = Some(val); + self + } +} + +/// Represents the content of a venue message to be sent as the result of +/// an inline query. +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InputMessageContentVenue { + /// Latitude of the venue in degrees. + pub latitude: f64, + + /// Longitude of the venue in degrees. + pub longitude: f64, + + /// Name of the venue. + pub title: String, + + /// Address of the venue. + pub address: String, + + /// Foursquare identifier of the venue, if known. + pub foursquare_id: Option, + + /// Foursquare type of the venue, if known. (For example, + /// `arts_entertainment/default`, `arts_entertainment/aquarium` + /// or `food/icecream`.) + pub foursquare_type: Option, +} + +impl InputMessageContentVenue { + pub fn new(latitude: f64, longitude: f64, title: S1, address: S2) -> Self + where + S1: Into, + S2: Into, + { + Self { + latitude, + longitude, + title: title.into(), + address: address.into(), + foursquare_id: None, + foursquare_type: None, + } + } + + pub fn latitude(mut self, val: f64) -> Self { + self.latitude = val; + self + } + + pub fn longitude(mut self, val: f64) -> Self { + self.longitude = val; + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = val.into(); + self + } + + pub fn address(mut self, val: S) -> Self + where + S: Into, + { + self.address = val.into(); + self + } + + pub fn foursquare_id(mut self, val: S) -> Self + where + S: Into, + { + self.foursquare_id = Some(val.into()); + self + } + + pub fn foursquare_type(mut self, val: S) -> Self + where + S: Into, + { + self.foursquare_type = Some(val.into()); + self + } +} + +/// Represents the content of a contact message to be sent as the result of +/// an inline query. +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct InputMessageContentContact { + /// Contact's phone number. + pub phone_number: String, + + /// Contact's first name. + pub first_name: String, + + /// Contact's last name. + pub last_name: Option, + + /// Additional data about the contact in the form of a [vCard], 0-2048 + /// bytes. + /// + /// [vCard]: https://en.wikipedia.org/wiki/VCard + pub vcard: Option, +} + +impl InputMessageContentContact { + pub fn new(phone_number: S1, first_name: S2) -> Self + where + S1: Into, + S2: Into, + { + Self { + phone_number: phone_number.into(), + first_name: first_name.into(), + last_name: None, + vcard: None, + } + } + + pub fn phone_number(mut self, val: S) -> Self + where + S: Into, + { + self.phone_number = val.into(); + self + } + + pub fn first_name(mut self, val: S) -> Self + where + S: Into, + { + self.first_name = val.into(); + self + } + + pub fn last_name(mut self, val: S) -> Self + where + S: Into, + { + self.last_name = Some(val.into()); + self + } + + pub fn vcard(mut self, val: S) -> Self + where + S: Into, + { + self.vcard = Some(val.into()); + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn text_serialize() { + let expected_json = r#"{"message_text":"text"}"#; + let text_content = InputMessageContent::Text(InputMessageContentText { + message_text: String::from("text"), + parse_mode: None, + disable_web_page_preview: None, + }); + + let actual_json = serde_json::to_string(&text_content).unwrap(); + assert_eq!(expected_json, actual_json); + } + + #[test] + fn location_serialize() { + let expected_json = r#"{"latitude":59.08,"longitude":38.4326}"#; + let location_content = InputMessageContent::Location(InputMessageContentLocation { + latitude: 59.08, + longitude: 38.4326, + live_period: None, + }); + + let actual_json = serde_json::to_string(&location_content).unwrap(); + assert_eq!(expected_json, actual_json); + } + + #[test] + fn venue_serialize() { + let expected_json = r#"{"latitude":59.08,"longitude":38.4326,"title":"some title","address":"some address"}"#; + let venue_content = InputMessageContent::Venue(InputMessageContentVenue { + latitude: 59.08, + longitude: 38.4326, + title: String::from("some title"), + address: String::from("some address"), + foursquare_id: None, + foursquare_type: None, + }); + + let actual_json = serde_json::to_string(&venue_content).unwrap(); + assert_eq!(expected_json, actual_json); + } + + #[test] + fn contact_serialize() { + let expected_json = r#"{"phone_number":"+3800000000","first_name":"jhon"}"#; + let contact_content = InputMessageContent::Contact(InputMessageContentContact { + phone_number: String::from("+3800000000"), + first_name: String::from("jhon"), + last_name: None, + vcard: None, + }); + + let actual_json = serde_json::to_string(&contact_content).unwrap(); + assert_eq!(expected_json, actual_json); + } +} diff --git a/src/types/invoice.rs b/src/types/invoice.rs new file mode 100644 index 00000000..979881e1 --- /dev/null +++ b/src/types/invoice.rs @@ -0,0 +1,91 @@ +use serde::{Deserialize, Serialize}; + +/// This object contains basic information about an invoice. +/// +/// [The official docs](https://core.telegram.org/bots/api#invoice). +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct Invoice { + /// Product name. + pub title: String, + + /// Product description. + pub description: String, + + /// Unique bot deep-linking parameter that can be used to generate this + /// invoice. + pub start_parameter: String, + + /// Three-letter ISO 4217 currency code. + pub currency: String, + + /// Total price in the smallest units of the currency (integer, **not** + /// float/double). For example, for a price of `US$ 1.45` pass `amount = + /// 145`. See the exp parameter in [`currencies.json`], it shows the number + /// of digits past the decimal point for each currency (2 for the + /// majority of currencies). + /// + /// [`currencies.json`]: https://core.telegram.org/bots/payments/currencies.json + pub total_amount: i32, +} + +impl Invoice { + pub fn new( + title: S1, + description: S2, + start_parameter: S3, + currency: S4, + total_amount: i32, + ) -> Self + where + S1: Into, + S2: Into, + S3: Into, + S4: Into, + { + Self { + title: title.into(), + description: description.into(), + start_parameter: start_parameter.into(), + currency: currency.into(), + total_amount, + } + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = val.into(); + self + } + + pub fn description(mut self, val: S) -> Self + where + S: Into, + { + self.description = val.into(); + self + } + + pub fn start_parameter(mut self, val: S) -> Self + where + S: Into, + { + self.start_parameter = val.into(); + self + } + + pub fn currency(mut self, val: S) -> Self + where + S: Into, + { + self.currency = val.into(); + self + } + + pub fn total_amount(mut self, val: i32) -> Self { + self.total_amount = val; + self + } +} diff --git a/src/types/keyboard_button.rs b/src/types/keyboard_button.rs new file mode 100644 index 00000000..fc37134a --- /dev/null +++ b/src/types/keyboard_button.rs @@ -0,0 +1,156 @@ +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. +/// +/// [The official docs](https://core.telegram.org/bots/api#keyboardbutton). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct KeyboardButton { + /// Text of the button. If none of the optional fields are used, it will + /// be sent as a message when the button is pressed. + pub text: String, + + /// Request something from user. + /// - If `Some(Contact)`, the user's phone number will be sent as a contact + /// when the button is pressed. Available in private chats only + /// - If `Some(Location)`, the user's current location will be sent when the + /// button is pressed. Available in private chats only + #[serde(flatten)] + pub request: Option, +} + +impl KeyboardButton { + 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)] +#[non_exhaustive] +pub enum ButtonRequest { + Location, + Contact, + KeyboardButtonPollType(KeyboardButtonPollType), +} + +/// Helper struct for (de)serializing [`ButtonRequest`](ButtonRequest) +#[serde_with_macros::skip_serializing_none] +#[derive(Serialize, Deserialize)] +#[non_exhaustive] +struct RawRequest { + /// If `true`, the user's phone number will be sent as a contact + /// when the button is pressed. Available in private chats only. + #[serde(rename = "request_contact")] + contact: Option, + + /// If `true`, the user's current location will be sent when the + /// button is pressed. Available in private chats only. + #[serde(rename = "request_location")] + location: Option, + + /// If specified, the user will be asked to create a poll and + /// send it to the bot when the button is pressed. Available in private + /// chats only. + #[serde(rename = "request_poll")] + poll: Option, +} + +impl<'de> Deserialize<'de> for ButtonRequest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let raw = RawRequest::deserialize(deserializer)?; + match raw { + RawRequest { contact: Some(_), location: Some(_), poll: Some(_) } => { + Err(D::Error::custom( + "`request_contact` and `request_location` fields are mutually exclusive, but \ + both were provided", + )) + } + RawRequest { contact: Some(_), .. } => Ok(Self::Contact), + RawRequest { location: Some(_), .. } => Ok(Self::Location), + RawRequest { poll: Some(poll_type), .. } => Ok(Self::KeyboardButtonPollType(poll_type)), + _ => Err(D::Error::custom( + "Either one of `request_contact` and `request_location` fields is required", + )), + } + } +} + +impl Serialize for ButtonRequest { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Self::Contact => { + RawRequest { contact: Some(True), location: None, poll: None }.serialize(serializer) + } + Self::Location => { + RawRequest { contact: None, location: Some(True), poll: None }.serialize(serializer) + } + Self::KeyboardButtonPollType(poll_type) => { + RawRequest { contact: None, location: None, poll: Some(poll_type.clone()) } + .serialize(serializer) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serialize_no_request() { + let button = KeyboardButton { text: String::from(""), request: None }; + let expected = r#"{"text":""}"#; + let actual = serde_json::to_string(&button).unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn serialize_request_contact() { + let button = + KeyboardButton { text: String::from(""), request: Some(ButtonRequest::Contact) }; + let expected = r#"{"text":"","request_contact":true}"#; + let actual = serde_json::to_string(&button).unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn deserialize_no_request() { + let json = r#"{"text":""}"#; + let expected = KeyboardButton { text: String::from(""), request: None }; + let actual = serde_json::from_str(json).unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn deserialize_request_contact() { + let json = r#"{"text":"","request_contact":true}"#; + let expected = + KeyboardButton { text: String::from(""), request: Some(ButtonRequest::Contact) }; + let actual = serde_json::from_str(json).unwrap(); + assert_eq!(expected, actual); + } +} diff --git a/src/types/keyboard_button_poll_type.rs b/src/types/keyboard_button_poll_type.rs new file mode 100644 index 00000000..6b24d540 --- /dev/null +++ b/src/types/keyboard_button_poll_type.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct KeyboardButtonPollType { + poll_type: String, +} + +impl KeyboardButtonPollType { + pub fn new(poll_type: S) -> Self + where + S: Into, + { + Self { poll_type: poll_type.into() } + } + + pub fn poll_type(mut self, val: S) -> Self + where + S: Into, + { + self.poll_type = val.into(); + self + } +} diff --git a/src/types/label_price.rs b/src/types/label_price.rs new file mode 100644 index 00000000..4c6841a9 --- /dev/null +++ b/src/types/label_price.rs @@ -0,0 +1,56 @@ +use serde::{Deserialize, Serialize}; + +/// This object represents a portion of the price for goods or services. +/// +/// [The official docs](https://core.telegram.org/bots/api#labeledprice). +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct LabeledPrice { + /// Portion label. + pub label: String, + + /// Price of the product in the smallest units of the [currency] (integer, + /// **not** float/double). For example, for a price of `US$ 1.45` pass + /// `amount = 145`. See the exp parameter in [`currencies.json`], it shows + /// the number of digits past the decimal point for each currency (2 + /// for the majority of currencies). + /// + /// [currency]: https://core.telegram.org/bots/payments#supported-currencies + /// [`currencies.json`]: https://core.telegram.org/bots/payments/currencies.json + pub amount: i32, +} + +impl LabeledPrice { + pub fn new(label: S, amount: i32) -> Self + where + S: Into, + { + Self { label: label.into(), amount } + } + + pub fn label(mut self, val: S) -> Self + where + S: Into, + { + self.label = val.into(); + self + } + + pub fn amount(mut self, val: i32) -> Self { + self.amount = val; + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serialize() { + let labeled_price = LabeledPrice { label: "Label".to_string(), amount: 60 }; + let expected = r#"{"label":"Label","amount":60}"#; + let actual = serde_json::to_string(&labeled_price).unwrap(); + assert_eq!(actual, expected); + } +} diff --git a/src/types/location.rs b/src/types/location.rs new file mode 100644 index 00000000..e34cee1a --- /dev/null +++ b/src/types/location.rs @@ -0,0 +1,28 @@ +use serde::{Deserialize, Serialize}; + +/// This object represents a point on the map. +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct Location { + /// Longitude as defined by sender. + pub longitude: f64, + + /// Latitude as defined by sender. + pub latitude: f64, +} + +impl Location { + pub fn new(longitude: f64, latitude: f64) -> Self { + Self { longitude, latitude } + } + + pub fn latitude(mut self, val: f64) -> Self { + self.latitude = val; + self + } + + pub fn longitude(mut self, val: f64) -> Self { + self.longitude = val; + self + } +} diff --git a/src/types/login_url.rs b/src/types/login_url.rs new file mode 100644 index 00000000..5c683df0 --- /dev/null +++ b/src/types/login_url.rs @@ -0,0 +1,54 @@ +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: +/// +///

+/// +///
+/// +/// [Telegram Login Widget]: https://core.telegram.org/widgets/login +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct LoginUrl { + pub url: String, + pub forward_text: Option, + pub bot_username: Option, + pub request_write_access: Option, +} + +impl LoginUrl { + pub fn url(mut self, val: S) -> Self + where + S: Into, + { + self.url = val.into(); + self + } + + pub fn forward_text(mut self, val: S) -> Self + where + S: Into, + { + self.forward_text = Some(val.into()); + self + } + + pub fn bot_username(mut self, val: S) -> Self + where + S: Into, + { + self.bot_username = Some(val.into()); + self + } + + pub fn request_write_access(mut self, val: bool) -> Self { + self.request_write_access = Some(val); + self + } +} diff --git a/src/types/mask_position.rs b/src/types/mask_position.rs new file mode 100644 index 00000000..8bca0273 --- /dev/null +++ b/src/types/mask_position.rs @@ -0,0 +1,58 @@ +use serde::{Deserialize, Serialize}; + +/// This object describes the position on faces where a mask should be placed by +/// default. +/// +/// [The official docs](https://core.telegram.org/bots/api#maskposition). +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MaskPosition { + /// The part of the face relative to which the mask should be placed. One + /// of `forehead`, `eyes`, `mouth`, or `chin`. + pub point: String, + + /// Shift by X-axis measured in widths of the mask scaled to the face size, + /// from left to right. For example, choosing `-1.0` will place mask just + /// to the left of the default mask position. + pub x_shift: f64, + + /// Shift by Y-axis measured in heights of the mask scaled to the face + /// size, from top to bottom. For example, `1.0` will place the mask just + /// below the default mask position. + pub y_shift: f64, + + /// Mask scaling coefficient. For example, `2.0` means double size. + pub scale: f64, +} + +impl MaskPosition { + pub fn new(point: S, x_shift: f64, y_shift: f64, scale: f64) -> Self + where + S: Into, + { + Self { point: point.into(), x_shift, y_shift, scale } + } + + pub fn point(mut self, val: S) -> Self + where + S: Into, + { + self.point = val.into(); + self + } + + pub fn x_shift(mut self, val: f64) -> Self { + self.x_shift = val; + self + } + + pub fn y_shift(mut self, val: f64) -> Self { + self.y_shift = val; + self + } + + pub fn scale(mut self, val: f64) -> Self { + self.scale = val; + self + } +} diff --git a/src/types/me.rs b/src/types/me.rs new file mode 100644 index 00000000..0b46a030 --- /dev/null +++ b/src/types/me.rs @@ -0,0 +1,57 @@ +use crate::types::User; +use serde::{Deserialize, Serialize}; + +/// Returned only in [`Bot::get_me`]. +/// +/// [`Bot::get_me`]: crate::Bot::get_me +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct Me { + #[serde(flatten)] + pub user: User, + + /// `true`, if the bot can be invited to groups. + pub can_join_groups: bool, + + /// `true`, if [privacy mode] is disabled for the bot. + /// + /// [privacy mode]: https://core.telegram.org/bots#privacy-mode + pub can_read_all_group_messages: bool, + + /// `true`, if the bot supports inline queries. + pub supports_inline_queries: bool, +} + +impl Me { + pub fn new( + user: User, + can_join_groups: bool, + can_read_all_group_messages: bool, + supports_inline_queries: bool, + ) -> Self { + Self { user, can_join_groups, can_read_all_group_messages, supports_inline_queries } + } + + pub fn user(mut self, val: User) -> Self { + self.user = val; + self + } + + #[warn(clippy::wrong_self_convention)] + pub fn can_join_groups(mut self, val: bool) -> Self { + self.can_join_groups = val; + self + } + + #[warn(clippy::wrong_self_convention)] + pub fn can_read_all_group_messages(mut self, val: bool) -> Self { + self.can_read_all_group_messages = val; + self + } + + #[warn(clippy::wrong_self_convention)] + pub fn supports_inline_queries(mut self, val: bool) -> Self { + self.supports_inline_queries = val; + self + } +} diff --git a/src/types/message.rs b/src/types/message.rs new file mode 100644 index 00000000..6404ebad --- /dev/null +++ b/src/types/message.rs @@ -0,0 +1,1753 @@ +#![allow(clippy::large_enum_variant)] + +use serde::{Deserialize, Serialize}; + +use crate::types::{ + chat::{ChatKind, PublicChatKind}, + Animation, Audio, Chat, ChatPublic, Contact, Dice, Document, Game, InlineKeyboardMarkup, + Invoice, Location, MessageEntity, PassportData, PhotoSize, Poll, PublicChatChannel, + PublicChatSupergroup, Sticker, SuccessfulPayment, True, User, Venue, Video, VideoNote, Voice, +}; + +/// This object represents a message. +/// +/// [The official docs](https://core.telegram.org/bots/api#message). +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct Message { + /// Unique message identifier inside this chat. + #[serde(rename = "message_id")] + pub id: i32, + + /// Date the message was sent in Unix time. + pub date: i32, + + /// Conversation the message belongs to. + pub chat: Chat, + + /// Bot through which the message was sent. + pub via_bot: Option, + + #[serde(flatten)] + pub kind: MessageKind, +} + +impl Message { + pub fn new(id: i32, date: i32, chat: Chat, kind: MessageKind) -> Self { + Self { id, date, chat, kind, via_bot: None } + } + + pub fn id(mut self, val: i32) -> Self { + self.id = val; + self + } + + pub fn date(mut self, val: i32) -> Self { + self.date = val; + self + } + + pub fn chat(mut self, val: Chat) -> Self { + self.chat = val; + self + } + + pub fn kind(mut self, val: MessageKind) -> Self { + self.kind = val; + self + } + + pub fn via_bot(mut self, val: User) -> Self { + self.via_bot = Some(val); + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +#[non_exhaustive] +pub enum MessageKind { + Common(MessageCommon), + NewChatMembers(MessageNewChatMembers), + LeftChatMember(MessageLeftChatMember), + NewChatTitle(MessageNewChatTitle), + NewChatPhoto(MessageNewChatPhoto), + DeleteChatPhoto(MessageDeleteChatPhoto), + GroupChatCreated(MessageGroupChatCreated), + SupergroupChatCreated(MessageSupergroupChatCreated), + ChannelChatCreated(MessageChannelChatCreated), + Migrate(MessageMigrate), + Pinned(MessagePinned), + Invoice(MessageInvoice), + SuccessfulPayment(MessageSuccessfulPayment), + ConnectedWebsite(MessageConnectedWebsite), + PassportData(MessagePassportData), + Dice(MessageDice), +} + +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MessageCommon { + /// Sender, empty for messages sent to channels. + pub from: Option, + + #[serde(flatten)] + pub forward_kind: ForwardKind, + + /// Date the message was last edited in Unix time. + pub edit_date: Option, + + #[serde(flatten)] + pub media_kind: MediaKind, + + /// Inline keyboard attached to the message. `login_url` buttons are + /// represented as ordinary `url` buttons. + pub reply_markup: Option, +} + +impl MessageCommon { + pub fn new(forward_kind: ForwardKind, media_kind: MediaKind) -> Self { + Self { from: None, forward_kind, edit_date: None, media_kind, reply_markup: None } + } + + pub fn from(mut self, val: User) -> Self { + self.from = Some(val); + self + } + + pub fn forward_kind(mut self, val: ForwardKind) -> Self { + self.forward_kind = val; + self + } + + pub fn edit_date(mut self, val: i32) -> Self { + self.edit_date = Some(val); + self + } + + pub fn media_kind(mut self, val: MediaKind) -> Self { + self.media_kind = val; + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MessageNewChatMembers { + /// New members that were added to the group or supergroup and + /// information about them (the bot itself may be one of these + /// members). + pub new_chat_members: Vec, +} + +impl MessageNewChatMembers { + pub fn new(new_chat_members: N) -> Self + where + N: Into>, + { + Self { new_chat_members: new_chat_members.into() } + } + + pub fn new_chat_members(mut self, val: N) -> Self + where + N: Into>, + { + self.new_chat_members = val.into(); + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MessageLeftChatMember { + /// A member was removed from the group, information about them (this + /// member may be the bot itself). + pub left_chat_member: User, +} + +impl MessageLeftChatMember { + pub fn new(left_chat_member: N) -> Self + where + N: Into, + { + Self { left_chat_member: left_chat_member.into() } + } + + pub fn left_chat_member(mut self, val: N) -> Self + where + N: Into, + { + self.left_chat_member = val.into(); + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MessageNewChatTitle { + /// A chat title was changed to this value. + pub new_chat_title: String, +} + +impl MessageNewChatTitle { + pub fn new(new_chat_title: N) -> Self + where + N: Into, + { + Self { new_chat_title: new_chat_title.into() } + } + + pub fn new_chat_title(mut self, val: N) -> Self + where + N: Into, + { + self.new_chat_title = val.into(); + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MessageNewChatPhoto { + /// A chat photo was change to this value. + pub new_chat_photo: Vec, +} + +impl MessageNewChatPhoto { + pub fn new(new_chat_photo: N) -> Self + where + N: Into>, + { + Self { new_chat_photo: new_chat_photo.into() } + } + + pub fn new_chat_photo(mut self, val: N) -> Self + where + N: Into>, + { + self.new_chat_photo = val.into(); + self + } +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MessageDeleteChatPhoto { + /// Service message: the chat photo was deleted. + pub delete_chat_photo: True, +} + +impl MessageDeleteChatPhoto { + pub fn new() -> Self { + Self::default() + } +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MessageGroupChatCreated { + /// Service message: the group has been created. + pub group_chat_created: True, +} + +impl MessageGroupChatCreated { + pub fn new() -> Self { + Self::default() + } +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MessageSupergroupChatCreated { + /// Service message: the supergroup has been created. This field can‘t + /// be received in a message coming through updates, because bot can’t + /// be a member of a supergroup when it is created. It can only be + /// found in `reply_to_message` if someone replies to a very first + /// message in a directly created supergroup. + pub supergroup_chat_created: True, +} + +impl MessageSupergroupChatCreated { + pub fn new() -> Self { + Self::default() + } +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MessageChannelChatCreated { + /// Service message: the channel has been created. This field can‘t be + /// received in a message coming through updates, because bot can’t be + /// a member of a channel when it is created. It can only be found in + /// `reply_to_message` if someone replies to a very first message in a + /// channel. + pub channel_chat_created: True, +} + +impl MessageChannelChatCreated { + pub fn new() -> Self { + Self::default() + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MessageMigrate { + /// The group has been migrated to a supergroup with the specified + /// identifier. This number may be greater than 32 bits and some + /// programming languages may have difficulty/silent defects in + /// interpreting it. But it is smaller than 52 bits, so a signed 64 bit + /// integer or double-precision float type are safe for storing this + /// identifier. + pub migrate_to_chat_id: i64, + + /// The supergroup has been migrated from a group with the specified + /// identifier. This number may be greater than 32 bits and some + /// programming languages may have difficulty/silent defects in + /// interpreting it. But it is smaller than 52 bits, so a signed 64 bit + /// integer or double-precision float type are safe for storing this + /// identifier. + pub migrate_from_chat_id: i64, +} + +impl MessageMigrate { + pub fn new(migrate_to_chat_id: i64, migrate_from_chat_id: i64) -> Self { + Self { migrate_to_chat_id, migrate_from_chat_id } + } + + pub fn migrate_to_chat_id(mut self, val: i64) -> Self { + self.migrate_to_chat_id = val; + self + } + + pub fn migrate_from_chat_id(mut self, val: i64) -> Self { + self.migrate_from_chat_id = val; + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MessagePinned { + /// Specified message was pinned. Note that the Message object in this + /// field will not contain further `reply_to_message` fields even if it + /// is itself a reply. + #[serde(rename = "pinned_message")] + pub pinned: Box, +} + +impl MessagePinned { + pub fn new(pinned: Message) -> Self { + Self { pinned: Box::new(pinned) } + } + + pub fn pinned(mut self, val: Message) -> Self { + self.pinned = Box::new(val); + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MessageInvoice { + /// Message is an invoice for a [payment], information about the + /// invoice. [More about payments Âģ]. + /// + /// [payment]: https://core.telegram.org/bots/api#payments + /// [More about payments Âģ]: https://core.telegram.org/bots/api#payments + pub invoice: Invoice, +} + +impl MessageInvoice { + pub fn new(invoice: Invoice) -> Self { + Self { invoice } + } + + pub fn invoice(mut self, val: Invoice) -> Self { + self.invoice = val; + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MessageSuccessfulPayment { + /// Message is a service message about a successful payment, + /// information about the payment. [More about payments Âģ]. + /// + /// [More about payments Âģ]: https://core.telegram.org/bots/api#payments + pub successful_payment: SuccessfulPayment, +} + +impl MessageSuccessfulPayment { + pub fn new(successful_payment: SuccessfulPayment) -> Self { + Self { successful_payment } + } + + pub fn successful_payment(mut self, val: SuccessfulPayment) -> Self { + self.successful_payment = val; + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MessageConnectedWebsite { + /// The domain name of the website on which the user has logged in. + /// [More about Telegram Login Âģ]. + /// + /// [More about Telegram Login Âģ]: https://core.telegram.org/widgets/login + pub connected_website: String, +} + +impl MessageConnectedWebsite { + pub fn new(connected_website: S) -> Self + where + S: Into, + { + Self { connected_website: connected_website.into() } + } + + pub fn connected_website(mut self, val: S) -> Self + where + S: Into, + { + self.connected_website = val.into(); + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MessagePassportData { + /// Telegram Passport data. + pub passport_data: PassportData, +} + +impl MessagePassportData { + pub fn new(passport_data: PassportData) -> Self { + Self { passport_data } + } + + pub fn passport_data(mut self, val: PassportData) -> Self { + self.passport_data = val; + self + } +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub enum ForwardedFrom { + #[serde(rename = "forward_from")] + User(User), + #[serde(rename = "forward_sender_name")] + SenderName(String), +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +#[non_exhaustive] +pub enum ForwardKind { + Channel(ForwardChannel), + NonChannel(ForwardNonChannel), + Origin(ForwardOrigin), +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct ForwardChannel { + #[serde(rename = "forward_date")] + pub date: i32, + + #[serde(rename = "forward_from_chat")] + pub chat: Chat, + + #[serde(rename = "forward_from_message_id")] + pub message_id: i32, + + #[serde(rename = "forward_signature")] + pub signature: Option, +} + +impl ForwardChannel { + pub fn new(date: i32, chat: Chat, message_id: i32) -> Self { + Self { date, chat, message_id, signature: None } + } + + pub fn date(mut self, val: i32) -> Self { + self.date = val; + self + } + + pub fn chat(mut self, val: Chat) -> Self { + self.chat = val; + self + } + + pub fn message_id(mut self, val: i32) -> Self { + self.message_id = val; + self + } + + pub fn signature(mut self, val: S) -> Self + where + S: Into, + { + self.signature = Some(val.into()); + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct ForwardNonChannel { + #[serde(rename = "forward_date")] + pub date: i32, + + #[serde(flatten)] + pub from: ForwardedFrom, +} + +impl ForwardNonChannel { + pub fn new(date: i32, from: ForwardedFrom) -> Self { + Self { date, from } + } + + pub fn date(mut self, val: i32) -> Self { + self.date = val; + self + } + + pub fn from(mut self, val: ForwardedFrom) -> Self { + self.from = val; + self + } +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct ForwardOrigin { + pub reply_to_message: Option>, +} + +impl ForwardOrigin { + pub fn new() -> Self { + Self::default() + } + + pub fn reply_to_message(mut self, val: Message) -> Self { + self.reply_to_message = Some(Box::new(val)); + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +#[non_exhaustive] +pub enum MediaKind { + Animation(MediaAnimation), + Audio(MediaAudio), + Contact(MediaContact), + Document(MediaDocument), + Game(MediaGame), + Location(MediaLocation), + Photo(MediaPhoto), + Poll(MediaPoll), + Sticker(MediaSticker), + Text(MediaText), + Video(MediaVideo), + VideoNote(MediaVideoNote), + Voice(MediaVoice), + Venue(MediaVenue), +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MediaAnimation { + /// Message is an animation, information about the animation. For + /// backward compatibility, when this field is set, the document field + /// will also be set. + pub animation: Animation, + + #[doc(hidden)] + /// "For backward compatibility" (c) Telegram Docs. + #[serde(skip)] + pub document: (), + + /// Caption for the animation, 0-1024 characters. + pub caption: Option, + + /// For messages with a caption, special entities like usernames, URLs, + /// bot commands, etc. that appear in the caption. + #[serde(default = "Vec::new")] + pub caption_entities: Vec, +} + +impl MediaAnimation { + pub fn new(animation: Animation, caption_entities: CE) -> Self + where + CE: Into>, + { + Self { animation, document: (), caption: None, caption_entities: caption_entities.into() } + } + + pub fn animation(mut self, val: Animation) -> Self { + self.animation = val; + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn caption_entities(mut self, val: CE) -> Self + where + CE: Into>, + { + self.caption_entities = val.into(); + self + } +} + +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MediaAudio { + /// Message is an audio file, information about the file. + pub audio: Audio, + + /// Caption for the audio, 0-1024 characters. + pub caption: Option, + + /// For messages with a caption, special entities like usernames, URLs, + /// bot commands, etc. that appear in the caption. + #[serde(default = "Vec::new")] + pub caption_entities: Vec, +} + +impl MediaAudio { + pub fn new(audio: Audio, caption_entities: CE) -> Self + where + CE: Into>, + { + Self { audio, caption: None, caption_entities: caption_entities.into() } + } + + pub fn audio(mut self, val: Audio) -> Self { + self.audio = val; + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn caption_entities(mut self, val: CE) -> Self + where + CE: Into>, + { + self.caption_entities = val.into(); + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MediaContact { + /// Message is a shared contact, information about the contact. + contact: Contact, +} + +impl MediaContact { + pub fn new(contact: Contact) -> Self { + Self { contact } + } + + pub fn contact(mut self, val: Contact) -> Self { + self.contact = val; + self + } +} + +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MediaDocument { + /// Message is a general file, information about the file. + pub document: Document, + + /// Caption for the document, 0-1024 characters. + pub caption: Option, + + /// For messages with a caption, special entities like usernames, URLs, + /// bot commands, etc. that appear in the caption. + #[serde(default = "Vec::new")] + pub caption_entities: Vec, +} + +impl MediaDocument { + pub fn new(document: Document, caption_entities: CE) -> Self + where + CE: Into>, + { + Self { document, caption: None, caption_entities: caption_entities.into() } + } + + pub fn document(mut self, val: Document) -> Self { + self.document = val; + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn caption_entities(mut self, val: CE) -> Self + where + CE: Into>, + { + self.caption_entities = val.into(); + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MediaGame { + /// Message is a game, information about the game. [More + /// about games Âģ]. + /// + /// [More about games Âģ]: https://core.telegram.org/bots/api#games + pub game: Game, +} + +impl MediaGame { + pub fn new(game: Game) -> Self { + Self { game } + } + + pub fn game(mut self, val: Game) -> Self { + self.game = val; + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MediaLocation { + /// Message is a shared location, information about the location. + pub location: Location, +} + +impl MediaLocation { + pub fn new(location: Location) -> Self { + Self { location } + } + + pub fn location(mut self, val: Location) -> Self { + self.location = val; + self + } +} + +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MediaPhoto { + /// Message is a photo, available sizes of the photo. + pub photo: Vec, + + /// Caption for the photo, 0-1024 characters. + pub caption: Option, + + /// For messages with a caption, special entities like usernames, URLs, + /// bot commands, etc. that appear in the caption. + #[serde(default = "Vec::new")] + pub caption_entities: Vec, + + /// The unique identifier of a media message group this message belongs + /// to. + pub media_group_id: Option, +} + +impl MediaPhoto { + pub fn new(photo: P, caption_entities: CE) -> Self + where + P: Into>, + CE: Into>, + { + Self { + photo: photo.into(), + caption: None, + caption_entities: caption_entities.into(), + media_group_id: None, + } + } + + pub fn photo

(mut self, val: P) -> Self + where + P: Into>, + { + self.photo = val.into(); + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn caption_entities(mut self, val: CE) -> Self + where + CE: Into>, + { + self.caption_entities = val.into(); + self + } + + pub fn media_group_id(mut self, val: S) -> Self + where + S: Into, + { + self.media_group_id = Some(val.into()); + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MediaPoll { + /// Message is a native poll, information about the poll. + pub poll: Poll, +} + +impl MediaPoll { + pub fn new(poll: Poll) -> Self { + Self { poll } + } + + pub fn poll(mut self, val: Poll) -> Self { + self.poll = val; + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MediaSticker { + /// Message is a sticker, information about the sticker. + pub sticker: Sticker, +} + +impl MediaSticker { + pub fn new(sticker: Sticker) -> Self { + Self { sticker } + } + + pub fn poll(mut self, val: Sticker) -> Self { + self.sticker = val; + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MediaText { + /// For text messages, the actual UTF-8 text of the message, 0-4096 + /// characters. + pub text: String, + + /// For text messages, special entities like usernames, URLs, bot + /// commands, etc. that appear in the text. + #[serde(default = "Vec::new")] + pub entities: Vec, +} + +impl MediaText { + pub fn new(text: S, entities: E) -> Self + where + S: Into, + E: Into>, + { + Self { text: text.into(), entities: entities.into() } + } + + pub fn text(mut self, val: S) -> Self + where + S: Into, + { + self.text = val.into(); + self + } + + pub fn entities(mut self, val: CE) -> Self + where + CE: Into>, + { + self.entities = val.into(); + self + } +} + +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MediaVideo { + /// Message is a video, information about the video. + pub video: Video, + + /// Caption for the video, 0-1024 characters. + pub caption: Option, + + /// For messages with a caption, special entities like usernames, URLs, + /// bot commands, etc. that appear in the caption. + #[serde(default = "Vec::new")] + pub caption_entities: Vec, + + /// The unique identifier of a media message group this message belongs + /// to. + pub media_group_id: Option, +} + +impl MediaVideo { + pub fn new(video: Video, caption_entities: CE) -> Self + where + CE: Into>, + { + Self { + video, + caption: None, + caption_entities: caption_entities.into(), + media_group_id: None, + } + } + + pub fn video(mut self, val: Video) -> Self { + self.video = val; + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn caption_entities(mut self, val: CE) -> Self + where + CE: Into>, + { + self.caption_entities = val.into(); + self + } + + pub fn media_group_id(mut self, val: S) -> Self + where + S: Into, + { + self.media_group_id = Some(val.into()); + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MediaVideoNote { + /// Message is a [video note], information about the video message. + /// + /// [video note]: https://telegram.org/blog/video-messages-and-telescope + pub video_note: VideoNote, +} + +impl MediaVideoNote { + pub fn new(video_note: VideoNote) -> Self { + Self { video_note } + } + + pub fn video_note(mut self, val: VideoNote) -> Self { + self.video_note = val; + self + } +} + +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MediaVoice { + /// Message is a voice message, information about the file. + pub voice: Voice, + + /// Caption for the voice, 0-1024 characters. + pub caption: Option, + + /// For messages with a caption, special entities like usernames, URLs, + /// bot commands, etc. that appear in the caption. + #[serde(default = "Vec::new")] + pub caption_entities: Vec, +} + +impl MediaVoice { + pub fn new(voice: Voice, caption_entities: CE) -> Self + where + CE: Into>, + { + Self { voice, caption: None, caption_entities: caption_entities.into() } + } + + pub fn voice(mut self, val: Voice) -> Self { + self.voice = val; + self + } + + pub fn caption(mut self, val: S) -> Self + where + S: Into, + { + self.caption = Some(val.into()); + self + } + + pub fn caption_entities(mut self, val: CE) -> Self + where + CE: Into>, + { + self.caption_entities = val.into(); + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MediaVenue { + /// Message is a venue, information about the venue. + pub venue: Venue, +} + +impl MediaVenue { + pub fn new(venue: Venue) -> Self { + Self { venue } + } + + pub fn venue(mut self, val: Venue) -> Self { + self.venue = val; + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MessageDice { + /// Message is a dice with random value from 1 to 6. + dice: Dice, +} + +mod getters { + use std::ops::Deref; + + use crate::types::{ + self, + message::{ + ForwardKind::NonChannel, + MessageKind::{ + ChannelChatCreated, Common, ConnectedWebsite, DeleteChatPhoto, GroupChatCreated, + Invoice, LeftChatMember, Migrate, NewChatMembers, NewChatPhoto, NewChatTitle, + PassportData, Pinned, SuccessfulPayment, SupergroupChatCreated, + }, + }, + Chat, ForwardChannel, ForwardKind, ForwardNonChannel, ForwardOrigin, ForwardedFrom, + MediaAnimation, MediaAudio, MediaContact, MediaDocument, MediaGame, MediaKind, + MediaLocation, MediaPhoto, MediaPoll, MediaSticker, MediaText, MediaVenue, MediaVideo, + MediaVideoNote, MediaVoice, Message, MessageChannelChatCreated, MessageCommon, + MessageConnectedWebsite, MessageDeleteChatPhoto, MessageEntity, MessageGroupChatCreated, + MessageInvoice, MessageLeftChatMember, MessageMigrate, MessageNewChatMembers, + MessageNewChatPhoto, MessageNewChatTitle, MessagePassportData, MessagePinned, + MessageSuccessfulPayment, MessageSupergroupChatCreated, PhotoSize, True, User, + }; + + /// Getters for [Message] fields from [telegram docs]. + /// + /// [Message]: crate::types::Message + /// [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<&User> { + match &self.kind { + Common(MessageCommon { 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> { + match &self.kind { + Common(MessageCommon { + forward_kind: NonChannel(ForwardNonChannel { from, .. }), + .. + }) => Some(from), + _ => None, + } + } + + pub fn forward_from_chat(&self) -> Option<&Chat> { + match &self.kind { + Common(MessageCommon { + forward_kind: ForwardKind::Channel(ForwardChannel { chat, .. }), + .. + }) => Some(chat), + _ => None, + } + } + + pub fn forward_from_message_id(&self) -> Option<&i32> { + match &self.kind { + Common(MessageCommon { + forward_kind: ForwardKind::Channel(ForwardChannel { message_id, .. }), + .. + }) => Some(message_id), + _ => None, + } + } + + pub fn forward_signature(&self) -> Option<&str> { + match &self.kind { + Common(MessageCommon { + forward_kind: ForwardKind::Channel(ForwardChannel { signature, .. }), + .. + }) => signature.as_ref().map(Deref::deref), + _ => None, + } + } + + pub fn forward_date(&self) -> Option<&i32> { + match &self.kind { + Common(MessageCommon { + forward_kind: ForwardKind::Channel(ForwardChannel { date, .. }), + .. + }) + | Common(MessageCommon { + forward_kind: ForwardKind::NonChannel(ForwardNonChannel { date, .. }), + .. + }) => Some(date), + _ => None, + } + } + + pub fn reply_to_message(&self) -> Option<&Message> { + match &self.kind { + Common(MessageCommon { + forward_kind: ForwardKind::Origin(ForwardOrigin { reply_to_message, .. }), + .. + }) => reply_to_message.as_ref().map(Deref::deref), + _ => None, + } + } + + pub fn edit_date(&self) -> Option<&i32> { + match &self.kind { + Common(MessageCommon { edit_date, .. }) => edit_date.as_ref(), + _ => None, + } + } + + pub fn media_group_id(&self) -> Option<&str> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Video(MediaVideo { media_group_id, .. }), + .. + }) + | Common(MessageCommon { + media_kind: MediaKind::Photo(MediaPhoto { media_group_id, .. }), + .. + }) => media_group_id.as_ref().map(Deref::deref), + _ => None, + } + } + + pub fn text(&self) -> Option<&str> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Text(MediaText { text, .. }), + .. + }) => Some(text), + _ => None, + } + } + + pub fn text_owned(&self) -> Option { + self.text().map(ToOwned::to_owned) + } + + pub fn entities(&self) -> Option<&[MessageEntity]> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Text(MediaText { entities, .. }), + .. + }) => Some(entities), + _ => None, + } + } + + pub fn caption_entities(&self) -> Option<&[MessageEntity]> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Animation(MediaAnimation { caption_entities, .. }), + .. + }) + | Common(MessageCommon { + media_kind: MediaKind::Audio(MediaAudio { caption_entities, .. }), + .. + }) + | Common(MessageCommon { + media_kind: MediaKind::Document(MediaDocument { caption_entities, .. }), + .. + }) + | Common(MessageCommon { + media_kind: MediaKind::Photo(MediaPhoto { caption_entities, .. }), + .. + }) + | Common(MessageCommon { + media_kind: MediaKind::Video(MediaVideo { caption_entities, .. }), + .. + }) + | Common(MessageCommon { + media_kind: MediaKind::Voice(MediaVoice { caption_entities, .. }), + .. + }) => Some(caption_entities), + _ => None, + } + } + + pub fn audio(&self) -> Option<&types::Audio> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Audio(MediaAudio { audio, .. }), + .. + }) => Some(audio), + _ => None, + } + } + + pub fn document(&self) -> Option<&types::Document> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Document(MediaDocument { document, .. }), + .. + }) => Some(document), + _ => None, + } + } + + pub fn animation(&self) -> Option<&types::Animation> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Animation(MediaAnimation { animation, .. }), + .. + }) => Some(animation), + _ => None, + } + } + + pub fn game(&self) -> Option<&types::Game> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Game(MediaGame { game, .. }), + .. + }) => Some(game), + _ => None, + } + } + + pub fn photo(&self) -> Option<&[PhotoSize]> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Photo(MediaPhoto { photo, .. }), + .. + }) => Some(photo), + _ => None, + } + } + + pub fn sticker(&self) -> Option<&types::Sticker> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Sticker(MediaSticker { sticker, .. }), + .. + }) => Some(sticker), + _ => None, + } + } + + pub fn video(&self) -> Option<&types::Video> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Video(MediaVideo { video, .. }), + .. + }) => Some(video), + _ => None, + } + } + + pub fn voice(&self) -> Option<&types::Voice> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Voice(MediaVoice { voice, .. }), + .. + }) => Some(voice), + _ => None, + } + } + + pub fn video_note(&self) -> Option<&types::VideoNote> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::VideoNote(MediaVideoNote { video_note, .. }), + .. + }) => Some(video_note), + _ => None, + } + } + + pub fn caption(&self) -> Option<&str> { + match &self.kind { + Common(MessageCommon { media_kind, .. }) => match media_kind { + MediaKind::Animation(MediaAnimation { caption, .. }) + | MediaKind::Audio(MediaAudio { caption, .. }) + | MediaKind::Document(MediaDocument { caption, .. }) + | MediaKind::Photo(MediaPhoto { caption, .. }) + | MediaKind::Video(MediaVideo { caption, .. }) + | MediaKind::Voice(MediaVoice { caption, .. }) => { + caption.as_ref().map(Deref::deref) + } + _ => None, + }, + _ => None, + } + } + + pub fn contact(&self) -> Option<&types::Contact> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Contact(MediaContact { contact, .. }), + .. + }) => Some(contact), + _ => None, + } + } + + pub fn location(&self) -> Option<&types::Location> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Location(MediaLocation { location, .. }), + .. + }) => Some(location), + _ => None, + } + } + + pub fn venue(&self) -> Option<&types::Venue> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Venue(MediaVenue { venue, .. }), + .. + }) => Some(venue), + _ => None, + } + } + + pub fn poll(&self) -> Option<&types::Poll> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Poll(MediaPoll { poll, .. }), + .. + }) => Some(poll), + _ => None, + } + } + + pub fn new_chat_members(&self) -> Option<&[User]> { + match &self.kind { + NewChatMembers(MessageNewChatMembers { new_chat_members }) => { + Some(new_chat_members.as_ref()) + } + _ => None, + } + } + + pub fn left_chat_member(&self) -> Option<&User> { + match &self.kind { + LeftChatMember(MessageLeftChatMember { left_chat_member }) => { + Some(left_chat_member) + } + _ => None, + } + } + + pub fn new_chat_title(&self) -> Option<&str> { + match &self.kind { + NewChatTitle(MessageNewChatTitle { new_chat_title }) => Some(new_chat_title), + _ => None, + } + } + + pub fn new_chat_photo(&self) -> Option<&[PhotoSize]> { + match &self.kind { + NewChatPhoto(MessageNewChatPhoto { new_chat_photo }) => Some(new_chat_photo), + _ => None, + } + } + + // TODO: OK, `Option` is weird, can we do something with it? + // mb smt like `is_delete_chat_photo(&self) -> bool`? + pub fn delete_chat_photo(&self) -> Option { + match &self.kind { + DeleteChatPhoto(MessageDeleteChatPhoto { delete_chat_photo }) => { + Some(*delete_chat_photo) + } + _ => None, + } + } + + pub fn group_chat_created(&self) -> Option { + match &self.kind { + GroupChatCreated(MessageGroupChatCreated { group_chat_created }) => { + Some(*group_chat_created) + } + _ => None, + } + } + + pub fn super_group_chat_created(&self) -> Option { + match &self.kind { + SupergroupChatCreated(MessageSupergroupChatCreated { supergroup_chat_created }) => { + Some(*supergroup_chat_created) + } + _ => None, + } + } + + pub fn channel_chat_created(&self) -> Option { + match &self.kind { + ChannelChatCreated(MessageChannelChatCreated { channel_chat_created }) => { + Some(*channel_chat_created) + } + _ => None, + } + } + + pub fn migrate_to_chat_id(&self) -> Option { + match &self.kind { + Migrate(MessageMigrate { migrate_to_chat_id, .. }) => Some(*migrate_to_chat_id), + _ => None, + } + } + + pub fn migrate_from_chat_id(&self) -> Option { + match &self.kind { + Migrate(MessageMigrate { migrate_from_chat_id, .. }) => Some(*migrate_from_chat_id), + _ => None, + } + } + + pub fn pinned_message(&self) -> Option<&Message> { + match &self.kind { + Pinned(MessagePinned { pinned }) => Some(pinned), + _ => None, + } + } + + pub fn invoice(&self) -> Option<&types::Invoice> { + match &self.kind { + Invoice(MessageInvoice { invoice }) => Some(invoice), + _ => None, + } + } + + pub fn successful_payment(&self) -> Option<&types::SuccessfulPayment> { + match &self.kind { + SuccessfulPayment(MessageSuccessfulPayment { successful_payment }) => { + Some(successful_payment) + } + _ => None, + } + } + + pub fn connected_website(&self) -> Option<&str> { + match &self.kind { + ConnectedWebsite(MessageConnectedWebsite { connected_website }) => { + Some(connected_website) + } + _ => None, + } + } + + pub fn passport_data(&self) -> Option<&types::PassportData> { + match &self.kind { + PassportData(MessagePassportData { passport_data }) => Some(passport_data), + _ => None, + } + } + + pub fn reply_markup(&self) -> Option<&types::InlineKeyboardMarkup> { + match &self.kind { + Common(MessageCommon { reply_markup, .. }) => reply_markup.as_ref(), + _ => None, + } + } + } +} + +impl Message { + pub fn url(&self) -> Option { + match &self.chat.kind { + ChatKind::Public(ChatPublic { + kind: PublicChatKind::Channel(PublicChatChannel { username: Some(username) }), + .. + }) + | ChatKind::Public(ChatPublic { + kind: + PublicChatKind::Supergroup(PublicChatSupergroup { + username: Some(username), .. + }), + .. + }) => Some( + reqwest::Url::parse(format!("https://t.me/{0}/{1}/", username, self.id).as_str()) + .unwrap(), + ), + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use serde_json::from_str; + + use crate::types::*; + + #[test] + fn de_media_forwarded() { + let json = r#"{ + "message_id": 198283, + "from": { + "id": 250918540, + "is_bot": false, + "first_name": "АĐŊĐ´Ņ€ĐĩĐš", + "last_name": "ВĐģĐ°ŅĐžĐ˛", + "username": "aka_dude", + "language_code": "en" + }, + "chat": { + "id": 250918540, + "first_name": "АĐŊĐ´Ņ€ĐĩĐš", + "last_name": "ВĐģĐ°ŅĐžĐ˛", + "username": "aka_dude", + "type": "private" + }, + "date": 1567927221, + "video": { + "duration": 13, + "width": 512, + "height": 640, + "mime_type": "video/mp4", + "thumb": { + "file_id": "AAQCAAOmBAACBf2oS53pByA-I4CWWCObDwAEAQAHbQADMWcAAhYE", + "file_unique_id":"", + "file_size": 10339, + "width": 256, + "height": 320 + }, + "file_id": "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE", + "file_unique_id":"", + "file_size": 1381334 + } + }"#; + let message = from_str::(json); + assert!(message.is_ok()); + } + + #[test] + fn de_media_group_forwarded() { + let json = r#"{ + "message_id": 198283, + "from": { + "id": 250918540, + "is_bot": false, + "first_name": "АĐŊĐ´Ņ€ĐĩĐš", + "last_name": "ВĐģĐ°ŅĐžĐ˛", + "username": "aka_dude", + "language_code": "en" + }, + "chat": { + "id": 250918540, + "first_name": "АĐŊĐ´Ņ€ĐĩĐš", + "last_name": "ВĐģĐ°ŅĐžĐ˛", + "username": "aka_dude", + "type": "private" + }, + "date": 1567927221, + "media_group_id": "12543417770506682", + "video": { + "duration": 13, + "width": 512, + "height": 640, + "mime_type": "video/mp4", + "thumb": { + "file_id": "AAQCAAOmBAACBf2oS53pByA-I4CWWCObDwAEAQAHbQADMWcAAhYE", + "file_unique_id":"", + "file_size": 10339, + "width": 256, + "height": 320 + }, + "file_id": "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE", + "file_unique_id":"", + "file_size": 1381334 + } + }"#; + let message = from_str::(json); + assert!(message.is_ok()); + } + + #[test] + fn de_text() { + let json = r#"{ + "message_id": 199785, + "from": { + "id": 250918540, + "is_bot": false, + "first_name": "АĐŊĐ´Ņ€ĐĩĐš", + "last_name": "ВĐģĐ°ŅĐžĐ˛", + "username": "aka_dude", + "language_code": "en" + }, + "chat": { + "id": 250918540, + "first_name": "АĐŊĐ´Ņ€ĐĩĐš", + "last_name": "ВĐģĐ°ŅĐžĐ˛", + "username": "aka_dude", + "type": "private" + }, + "date": 1568289890, + "text": "ЛоĐģ ĐēĐĩĐē 😂" + }"#; + let message = from_str::(json); + assert!(message.is_ok()); + } + + #[test] + fn de_sticker() { + let json = r#"{ + "message_id": 199787, + "from": { + "id": 250918540, + "is_bot": false, + "first_name": "АĐŊĐ´Ņ€ĐĩĐš", + "last_name": "ВĐģĐ°ŅĐžĐ˛", + "username": "aka_dude", + "language_code": "en" + }, + "chat": { + "id": 250918540, + "first_name": "АĐŊĐ´Ņ€ĐĩĐš", + "last_name": "ВĐģĐ°ŅĐžĐ˛", + "username": "aka_dude", + "type": "private" + }, + "date": 1568290188, + "sticker": { + "width": 512, + "height": 512, + "emoji": "😡", + "set_name": "AdvenTimeAnim", + "is_animated": true, + "thumb": { + "file_id": "AAQCAAMjAAOw0PgMaabKAcaXKCBLubkPAAQBAAdtAAPGKwACFgQ", + "file_unique_id":"", + "file_size": 4118, + "width": 128, + "height": 128 + }, + "file_id": "CAADAgADIwADsND4DGmmygHGlyggFgQ", + "file_unique_id":"", + "file_size": 16639 + } + }"#; + let message = from_str::(json); + assert!(message.is_ok()); + } + + #[test] + fn de_image() { + let json = r#"{ + "message_id": 199791, + "from": { + "id": 250918540, + "is_bot": false, + "first_name": "АĐŊĐ´Ņ€ĐĩĐš", + "last_name": "ВĐģĐ°ŅĐžĐ˛", + "username": "aka_dude", + "language_code": "en" + }, + "chat": { + "id": 250918540, + "first_name": "АĐŊĐ´Ņ€ĐĩĐš", + "last_name": "ВĐģĐ°ŅĐžĐ˛", + "username": "aka_dude", + "type": "private" + }, + "date": 1568290622, + "photo": [ + { + "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA20AAybcBAABFgQ", + "file_unique_id":"", + "file_size": 18188, + "width": 320, + "height": 239 + }, + { + "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA3gAAyfcBAABFgQ", + "file_unique_id":"", + "file_size": 62123, + "width": 800, + "height": 598 + }, + { + "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA3kAAyTcBAABFgQ", + "file_unique_id":"", + "file_size": 75245, + "width": 962, + "height": 719 + } + ] + }"#; + let message = from_str::(json); + assert!(message.is_ok()); + } +} diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs new file mode 100644 index 00000000..c39459be --- /dev/null +++ b/src/types/message_entity.rs @@ -0,0 +1,162 @@ +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. +/// +/// [The official docs](https://core.telegram.org/bots/api#messageentity). +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct MessageEntity { + #[serde(flatten)] + pub kind: MessageEntityKind, + + /// Offset in UTF-16 code units to the start of the entity. + pub offset: usize, + + /// Length of the entity in UTF-16 code units. + pub length: usize, +} + +impl MessageEntity { + pub fn new(kind: MessageEntityKind, offset: usize, length: usize) -> Self { + Self { kind, offset, length } + } + + pub fn kind(mut self, val: MessageEntityKind) -> Self { + self.kind = val; + self + } + + pub fn offset(mut self, val: usize) -> Self { + self.offset = val; + self + } + + pub fn length(mut self, val: usize) -> Self { + self.length = val; + self + } +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(tag = "type")] +#[non_exhaustive] +pub enum MessageEntityKind { + Mention, + Hashtag, + Cashtag, + BotCommand, + Url, + Email, + PhoneNumber, + Bold, + Italic, + Code, + Pre { language: Option }, + TextLink { url: String }, + TextMention { user: User }, + Underline, + Strikethrough, +} + +impl MessageEntity { + pub fn text_from(&self, message: &Message) -> Option { + let text = message.text(); + Some(String::from(&text?[self.offset..self.offset + self.length])) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::{ + Chat, ChatKind, ChatPrivate, ForwardKind, ForwardOrigin, MediaKind, MediaText, + MessageCommon, MessageKind, + }; + + #[test] + fn recursive_kind() { + use serde_json::from_str; + + assert_eq!( + MessageEntity { + kind: MessageEntityKind::TextLink { url: "ya.ru".into() }, + offset: 1, + length: 2, + }, + from_str::( + r#"{"type":"text_link","url":"ya.ru","offset":1,"length":2}"# + ) + .unwrap() + ); + } + + #[test] + fn pre() { + use serde_json::from_str; + + assert_eq!( + MessageEntity { + kind: MessageEntityKind::Pre { language: Some("rust".to_string()) }, + offset: 1, + length: 2, + }, + from_str::( + r#"{"type":"pre","url":"ya.ru","offset":1,"length":2,"language":"rust"}"# + ) + .unwrap() + ); + } + + #[test] + fn text_from() { + let message = message(); + let expected = Some("yes".to_string()); + let entity = message.entities().unwrap()[0].clone(); + let actual = entity.text_from(&message); + assert_eq!(actual, expected); + } + + fn message() -> Message { + Message { + via_bot: None, + id: 0, + date: 0, + chat: Chat { + id: 0, + kind: ChatKind::Private(ChatPrivate { + type_: (), + username: None, + first_name: None, + last_name: None, + }), + photo: None, + }, + kind: MessageKind::Common(MessageCommon { + from: Some(User { + id: 0, + is_bot: false, + first_name: "".to_string(), + last_name: None, + username: None, + language_code: None, + }), + forward_kind: ForwardKind::Origin(ForwardOrigin { reply_to_message: None }), + edit_date: None, + media_kind: MediaKind::Text(MediaText { + text: "no yes no".to_string(), + entities: vec![MessageEntity { + kind: MessageEntityKind::Mention, + offset: 3, + length: 3, + }], + }), + reply_markup: None, + }), + } + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 00000000..e6f7e5fa --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,194 @@ +//! API types. + +pub use allowed_update::*; +pub use animation::*; +pub use audio::*; +pub use bot_command::*; +pub use callback_game::*; +pub use callback_query::*; +pub use chat::*; +pub use chat_action::*; +pub use chat_id::*; +pub use chat_member::*; +pub use chat_permissions::*; +pub use chat_photo::*; +pub use chosen_inline_result::*; +pub use contact::*; +pub use dice::*; +pub use dice_emoji::*; +pub use document::*; +pub use encrypted_credentials::*; +pub use encrypted_passport_element::*; +pub use file::*; +pub use force_reply::*; +pub use game::*; +pub use game_high_score::*; +pub use inline_keyboard_button::*; +pub use inline_keyboard_markup::*; +pub use inline_query::*; +pub use inline_query_result::*; +pub use inline_query_result_article::*; +pub use inline_query_result_audio::*; +pub use inline_query_result_cached_audio::*; +pub use inline_query_result_cached_document::*; +pub use inline_query_result_cached_gif::*; +pub use inline_query_result_cached_mpeg4_gif::*; +pub use inline_query_result_cached_photo::*; +pub use inline_query_result_cached_sticker::*; +pub use inline_query_result_cached_video::*; +pub use inline_query_result_cached_voice::*; +pub use inline_query_result_contact::*; +pub use inline_query_result_document::*; +pub use inline_query_result_game::*; +pub use inline_query_result_gif::*; +pub use inline_query_result_location::*; +pub use inline_query_result_mpeg4_gif::*; +pub use inline_query_result_photo::*; +pub use inline_query_result_venue::*; +pub use inline_query_result_video::*; +pub use inline_query_result_voice::*; +pub use input_file::*; +pub use input_media::*; +pub use input_message_content::*; +pub use invoice::*; +pub use keyboard_button::*; +pub use keyboard_button_poll_type::*; +pub use label_price::*; +pub use location::*; +pub use login_url::*; +pub use mask_position::*; +pub use me::*; +pub use message::*; +pub use message_entity::*; +pub use order_info::*; +pub use parse_mode::*; +pub use passport_data::*; +pub use passport_element_error::*; +pub use passport_file::*; +pub use photo_size::*; +pub use poll::*; +pub use poll_answer::*; +pub use poll_type::*; +pub use pre_checkout_query::*; +pub use reply_keyboard_markup::*; +pub use reply_keyboard_remove::*; +pub use reply_markup::*; +pub use response_parameters::*; +pub use send_invoice::*; +pub use shipping_address::*; +pub use shipping_option::*; +pub use shipping_query::*; +pub use sticker::*; +pub use sticker_set::*; +pub use sticker_type::*; +pub use successful_payment::*; +pub use target_message::*; +pub use unit_false::*; +pub use unit_true::*; +pub use update::*; +pub use user::*; +pub use user_profile_photos::*; +pub use venue::*; +pub use video::*; +pub use video_note::*; +pub use voice::*; +pub use webhook_info::*; + +mod allowed_update; +mod animation; +mod audio; +mod bot_command; +mod callback_game; +mod callback_query; +mod chat; +mod chat_action; +mod chat_id; +mod chat_member; +mod chat_permissions; +mod chat_photo; +mod chosen_inline_result; +mod contact; +mod dice; +mod dice_emoji; +mod document; +mod file; +mod force_reply; +mod game; +mod game_high_score; +mod inline_keyboard_button; +mod inline_keyboard_markup; +mod input_file; +mod input_media; +mod input_message_content; +mod invoice; +mod keyboard_button; +mod keyboard_button_poll_type; +mod label_price; +mod location; +mod login_url; +mod mask_position; +mod me; +mod message; +mod message_entity; +mod order_info; +mod parse_mode; +mod photo_size; +mod poll; +mod poll_answer; +mod poll_type; +mod pre_checkout_query; +mod reply_keyboard_markup; +mod reply_keyboard_remove; +mod reply_markup; +mod response_parameters; +mod send_invoice; +mod shipping_address; +mod shipping_option; +mod shipping_query; +mod sticker; +mod sticker_set; +mod sticker_type; +mod successful_payment; +mod target_message; +mod unit_false; +mod unit_true; +mod update; +mod user; +mod user_profile_photos; +mod venue; +mod video; +mod video_note; +mod voice; +mod webhook_info; + +mod inline_query; +mod inline_query_result; +mod inline_query_result_article; +mod inline_query_result_audio; +mod inline_query_result_cached_audio; +mod inline_query_result_cached_document; +mod inline_query_result_cached_gif; +mod inline_query_result_cached_mpeg4_gif; +mod inline_query_result_cached_photo; +mod inline_query_result_cached_sticker; +mod inline_query_result_cached_video; +mod inline_query_result_cached_voice; +mod inline_query_result_contact; +mod inline_query_result_document; +mod inline_query_result_game; +mod inline_query_result_gif; +mod inline_query_result_location; +mod inline_query_result_mpeg4_gif; +mod inline_query_result_photo; +mod inline_query_result_venue; +mod inline_query_result_video; +mod inline_query_result_voice; + +mod encrypted_credentials; +mod encrypted_passport_element; +mod passport_data; +mod passport_element_error; +mod passport_file; + +pub use non_telegram_types::*; +mod non_telegram_types; diff --git a/src/types/non_telegram_types/country_code.rs b/src/types/non_telegram_types/country_code.rs new file mode 100644 index 00000000..4f7705ac --- /dev/null +++ b/src/types/non_telegram_types/country_code.rs @@ -0,0 +1,254 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub enum CountryCode { + AD, + AE, + AF, + AG, + AI, + AL, + AM, + AO, + AQ, + AR, + AS, + AT, + AU, + AW, + AX, + AZ, + BA, + BB, + BD, + BE, + BF, + BG, + BH, + BI, + BJ, + BL, + BM, + BN, + BO, + BQ, + BR, + BS, + BT, + BV, + BW, + BY, + BZ, + CA, + CC, + CD, + CF, + CG, + CH, + CI, + CK, + CL, + CM, + CN, + CO, + CR, + CU, + CV, + CW, + CX, + CY, + CZ, + DE, + DJ, + DK, + DM, + DO, + DZ, + EC, + EE, + EG, + EH, + ER, + ES, + ET, + FI, + FJ, + FK, + FM, + FO, + FR, + GA, + GB, + GD, + GE, + GF, + GG, + GH, + GI, + GL, + GM, + GN, + GP, + GQ, + GR, + GS, + GT, + GU, + GW, + GY, + HK, + HM, + HN, + HR, + HT, + HU, + ID, + IE, + IL, + IM, + IN, + IO, + IQ, + IR, + IS, + IT, + JE, + JM, + JO, + JP, + KE, + KG, + KH, + KI, + KM, + KN, + KP, + KR, + KW, + KY, + KZ, + LA, + LB, + LC, + LI, + LK, + LR, + LS, + LT, + LU, + LV, + LY, + MA, + MC, + MD, + ME, + MF, + MG, + MH, + MK, + ML, + MM, + MN, + MO, + MP, + MQ, + MR, + MS, + MT, + MU, + MV, + MW, + MX, + MY, + MZ, + NA, + NC, + NE, + NF, + NG, + NI, + NL, + NO, + NP, + NR, + NU, + NZ, + OM, + PA, + PE, + PF, + PG, + PH, + PK, + PL, + PM, + PN, + PR, + PS, + PT, + PW, + PY, + QA, + RE, + RO, + RS, + RU, + RW, + SA, + SB, + SC, + SD, + SE, + SG, + SH, + SI, + SJ, + SK, + SL, + SM, + SN, + SO, + SR, + SS, + ST, + SV, + SX, + SY, + SZ, + TC, + TD, + TF, + TG, + TH, + TJ, + TK, + TL, + TM, + TN, + TO, + TR, + TT, + TV, + TW, + TZ, + UA, + UG, + UM, + US, + UY, + UZ, + VA, + VC, + VE, + VG, + VI, + VN, + VU, + WF, + WS, + YE, + YT, + ZA, + ZM, + ZW, +} diff --git a/src/types/non_telegram_types/currency.rs b/src/types/non_telegram_types/currency.rs new file mode 100644 index 00000000..0cdebce6 --- /dev/null +++ b/src/types/non_telegram_types/currency.rs @@ -0,0 +1,89 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub enum Currency { + AED, + AFN, + ALL, + AMD, + ARS, + AUD, + AZN, + BAM, + BDT, + BGN, + BND, + BOB, + BRL, + CAD, + CHF, + CLP, + CNY, + COP, + CRC, + CZK, + DKK, + DOP, + DZD, + EGP, + EUR, + GBP, + GEL, + GTQ, + HKD, + HNL, + HRK, + HUF, + IDR, + ILS, + INR, + ISK, + JMD, + JPY, + KES, + KGS, + KRW, + KZT, + LBP, + LKR, + MAD, + MDL, + MNT, + MUR, + MVR, + MXN, + MYR, + MZN, + NGN, + NIO, + NOK, + NPR, + NZD, + PAB, + PEN, + PHP, + PKR, + PLN, + PYG, + QAR, + RON, + RSD, + RUB, + SAR, + SEK, + SGD, + THB, + TJS, + TRY, + TTD, + TWD, + TZS, + UAH, + UGX, + USD, + UYU, + UZS, + VND, + YER, + ZAR, +} diff --git a/src/types/non_telegram_types/mime_wrapper.rs b/src/types/non_telegram_types/mime_wrapper.rs new file mode 100644 index 00000000..43326de6 --- /dev/null +++ b/src/types/non_telegram_types/mime_wrapper.rs @@ -0,0 +1,44 @@ +use derive_more::From; +use mime::Mime; +use serde::{de::Visitor, export::Formatter, Deserialize, Deserializer, Serialize, Serializer}; + +/// Serializable & deserializable `MIME` wrapper. +#[derive(Clone, Debug, Eq, Hash, PartialEq, From)] +pub struct MimeWrapper(pub Mime); + +impl Serialize for MimeWrapper { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + serializer.serialize_str(self.0.as_ref()) + } +} + +struct MimeVisitor; +impl<'a> Visitor<'a> for MimeVisitor { + type Value = MimeWrapper; + + fn expecting(&self, formatter: &mut Formatter<'_>) -> Result<(), serde::export::fmt::Error> { + formatter.write_str("mime type") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + match v.parse::() { + Ok(mime_type) => Ok(MimeWrapper(mime_type)), + Err(e) => Err(E::custom(e)), + } + } +} + +impl<'de> Deserialize<'de> for MimeWrapper { + fn deserialize(deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(MimeVisitor) + } +} diff --git a/src/types/non_telegram_types/mod.rs b/src/types/non_telegram_types/mod.rs new file mode 100644 index 00000000..8add54b6 --- /dev/null +++ b/src/types/non_telegram_types/mod.rs @@ -0,0 +1,7 @@ +pub use country_code::*; +pub use currency::*; +pub use mime_wrapper::*; + +mod country_code; +mod currency; +mod mime_wrapper; diff --git a/src/types/order_info.rs b/src/types/order_info.rs new file mode 100644 index 00000000..7f41f7a9 --- /dev/null +++ b/src/types/order_info.rs @@ -0,0 +1,72 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::ShippingAddress; + +/// This object represents information about an order. +/// +/// [The official docs](https://core.telegram.org/bots/api#orderinfo). +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct OrderInfo { + /// User's name. + pub name: String, + + /// User's phone number. + pub phone_number: String, + + /// User's email. + pub email: String, + + /// User's shipping address. + pub shipping_address: ShippingAddress, +} + +impl OrderInfo { + pub fn new( + name: S1, + phone_number: S2, + email: S3, + shipping_address: ShippingAddress, + ) -> Self + where + S1: Into, + S2: Into, + S3: Into, + { + Self { + name: name.into(), + phone_number: phone_number.into(), + email: email.into(), + shipping_address, + } + } + + pub fn name(mut self, val: S) -> Self + where + S: Into, + { + self.name = val.into(); + self + } + + pub fn phone_number(mut self, val: S) -> Self + where + S: Into, + { + self.phone_number = val.into(); + self + } + + pub fn email(mut self, val: S) -> Self + where + S: Into, + { + self.email = val.into(); + self + } + + pub fn shipping_address(mut self, val: ShippingAddress) -> Self { + self.shipping_address = val; + self + } +} diff --git a/src/types/parse_mode.rs b/src/types/parse_mode.rs new file mode 100644 index 00000000..44a0ecff --- /dev/null +++ b/src/types/parse_mode.rs @@ -0,0 +1,189 @@ +// see https://github.com/rust-lang/rust/issues/38832 +// (for built ins there no warnings, but for (De)Serialize, there are) +#![allow(deprecated)] + +use std::{ + convert::{TryFrom, TryInto}, + str::FromStr, +}; + +use serde::{Deserialize, Serialize}; + +/// 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 +/// them accordingly. You can use either markdown-style or HTML-style +/// formatting. +/// +/// Note that Telegram clients will display an **alert** to the user before +/// opening an inline link (‘Open this link?’ together with the full URL). +/// +/// Links `tg://user?id=` can be used to mention a user by their ID +/// without using a username. Please note: +/// +/// - These links will work **only** if they are used inside an inline link. For +/// example, they will not work, when used in an inline keyboard button or in +/// a message text. +/// - These mentions are only guaranteed to work if the user has contacted the +/// bot in the past, has sent a callback query to the bot via inline button or +/// is a member in the group where he was mentioned. +/// +/// ## MarkdownV2 style +/// +/// To use this mode, pass [`MarkdownV2`] in the `parse_mode` field. +/// Use the following syntax in your message: +/// ````text +/// *bold \*text* +/// _italic \*text_ +/// __underline__ +/// ~strikethrough~ +/// *bold _italic bold ~italic bold strikethrough~ __underline italic bold___ bold* +/// [inline URL](http://www.example.com/) +/// [inline mention of a user](tg://user?id=123456789) +/// `inline fixed-width code` +/// ``` +/// pre-formatted fixed-width code block +/// ``` +/// ```rust +/// pre-formatted fixed-width code block written in the Rust programming +/// language ``` +/// ```` +/// +/// Please note: +/// - Any character between 1 and 126 inclusively can be escaped anywhere with a +/// preceding '\' character, in which case it is treated as an ordinary +/// character and not a part of the markup. +/// - Inside `pre` and `code` entities, all '`‘ and ’\‘ characters must be +/// escaped with a preceding ’\' character. +/// - Inside `(...)` part of inline link definition, all ')‘ and ’\‘ must be +/// escaped with a preceding ’\' character. +/// - In all other places characters ’_‘, ’*‘, ’[‘, ’]‘, ’(‘, ’)‘, ’~‘, ’`‘, +/// ’>‘, ’#‘, ’+‘, ’+‘, ’-‘, ’|‘, ’{‘, ’}‘, ’.‘, ’!‘ must be escaped with the +/// preceding character ’\'. +/// - In case of ambiguity between `italic` and `underline` entities ‘__’ is +/// always greadily treated from left to right as beginning or end of +/// `underline` entity, so instead of `___italic underline___` use `___italic +/// underline_\r__`, where `\r` is a character with code `13`, which will be +/// ignored. +/// +/// ## HTML style +/// To use this mode, pass [`HTML`] in the `parse_mode` field. +/// The following tags are currently supported: +/// ````text +/// bold, bold +/// italic, italic +/// underline, underline +/// strikethrough, strikethrough, +/// strikethrough bold italic bold italic bold +/// strikethrough underline italic bold bold inline URL +/// inline mention of a user +/// inline fixed-width code +///

pre-formatted fixed-width code block
+///
pre-formatted fixed-width code block
+/// written in the Rust programming language
```` +/// +/// Please note: +/// +/// - Only the tags mentioned above are currently supported. +/// - All `<`, `>` and `&` symbols that are not a part of a tag or an HTML +/// entity must be replaced with the corresponding HTML entities (`<` with +/// `<`, `>` with `>` and `&` with `&`). +/// - All numerical HTML entities are supported. +/// - The API currently supports only the following named HTML entities: `<`, +/// `>`, `&` and `"`. +/// - Use nested `pre` and `code` tags, to define programming language for `pre` +/// entity. +/// - Programming language can't be specified for standalone `code` tags. +/// +/// ## Markdown style +/// This is a legacy mode, retained for backward compatibility. To use this +/// mode, pass [`Markdown`] in the `parse_mode` field. +/// Use the following syntax in your message: +/// ````text +/// *bold text* +/// _italic text_ +/// [inline URL](http://www.example.com/) +/// [inline mention of a user](tg://user?id=123456789) +/// `inline fixed-width code` +/// ```rust +/// pre-formatted fixed-width code block written in the Rust programming +/// language ``` +/// ```` +/// +/// Please note: +/// - Entities must not be nested, use parse mode [`MarkdownV2`] instead. +/// - There is no way to specify underline and strikethrough entities, use parse +/// mode [`MarkdownV2`] instead. +/// - To escape characters ’_‘, ’*‘, ’`‘, ’[‘ outside of an entity, prepend the +/// characters ’\' before them. +/// - Escaping inside entities is not allowed, so entity must be closed first +/// and reopened again: use `_snake_\__case_` for italic `snake_case` and +/// `*2*\**2=4*` for bold `2*2=4`. +/// +/// [`MarkdownV2`]: ParseMode::MarkdownV2 +/// [`HTML`]: ParseMode::HTML +/// [`Markdown`]: ParseMode::Markdown +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub enum ParseMode { + MarkdownV2, + HTML, + #[deprecated = "This is a legacy mode, retained for backward compatibility. Use `MarkdownV2` \ + instead."] + Markdown, +} + +impl TryFrom<&str> for ParseMode { + type Error = (); + + fn try_from(value: &str) -> Result { + let normalized = value.to_lowercase(); + match normalized.as_ref() { + "html" => Ok(ParseMode::HTML), + "markdown" => Ok(ParseMode::Markdown), + "markdownv2" => Ok(ParseMode::MarkdownV2), + _ => Err(()), + } + } +} + +impl TryFrom for ParseMode { + type Error = (); + + fn try_from(value: String) -> Result { + value.as_str().try_into() + } +} + +impl FromStr for ParseMode { + type Err = (); + + fn from_str(s: &str) -> Result { + s.try_into() + } +} + +#[cfg(test)] +mod tests { + #![allow(deprecated)] + + use super::*; + + #[test] + fn html_serialization() { + let expected_json = String::from(r#""HTML""#); + let actual_json = serde_json::to_string(&ParseMode::HTML).unwrap(); + + assert_eq!(expected_json, actual_json) + } + + #[test] + fn markdown_serialization() { + let expected_json = String::from(r#""Markdown""#); + let actual_json = serde_json::to_string(&ParseMode::Markdown).unwrap(); + + assert_eq!(expected_json, actual_json) + } +} diff --git a/src/types/passport_data.rs b/src/types/passport_data.rs new file mode 100644 index 00000000..3abaef93 --- /dev/null +++ b/src/types/passport_data.rs @@ -0,0 +1,40 @@ +use serde::{Deserialize, Serialize}; + +use super::{EncryptedCredentials, EncryptedPassportElement}; + +/// Contains information about Telegram Passport data shared with the bot by the +/// user. +/// +/// [The official docs](https://core.telegram.org/bots/api#passportdata). +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct PassportData { + /// Array with information about documents and other Telegram Passport + /// elements that was shared with the bot. + pub data: Vec, + + /// Encrypted credentials required to decrypt the data. + pub credentials: EncryptedCredentials, +} + +impl PassportData { + pub fn new(data: E, credentials: EncryptedCredentials) -> Self + where + E: Into>, + { + Self { data: data.into(), credentials } + } + + pub fn data(mut self, val: E) -> Self + where + E: Into>, + { + self.data = val.into(); + self + } + + pub fn credentials(mut self, val: EncryptedCredentials) -> Self { + self.credentials = val; + self + } +} diff --git a/src/types/passport_element_error.rs b/src/types/passport_element_error.rs new file mode 100644 index 00000000..521584e5 --- /dev/null +++ b/src/types/passport_element_error.rs @@ -0,0 +1,558 @@ +use serde::{Deserialize, Serialize}; + +/// This object represents an error in the Telegram Passport element which was +/// submitted that should be resolved by the user. +/// +/// [The official docs](https://core.telegram.org/bots/api#passportelementerror). +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct PassportElementError { + /// Error message. + message: String, + + #[serde(flatten)] + kind: PassportElementErrorKind, +} + +impl PassportElementError { + pub fn new(message: S, kind: PassportElementErrorKind) -> Self + where + S: Into, + { + Self { message: message.into(), kind } + } + + pub fn message(mut self, val: S) -> Self + where + S: Into, + { + self.message = val.into(); + self + } + + pub fn kind(mut self, val: PassportElementErrorKind) -> Self { + self.kind = val; + self + } +} + +#[serde(tag = "source")] +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub enum PassportElementErrorKind { + #[serde(rename = "data")] + DataField(PassportElementErrorDataField), + + #[serde(rename = "snake_case")] + FrontSide(PassportElementErrorFrontSide), + + #[serde(rename = "snake_case")] + ReverseSide(PassportElementErrorReverseSide), + + #[serde(rename = "snake_case")] + Selfie(PassportElementErrorSelfie), + + #[serde(rename = "snake_case")] + File(PassportElementErrorFile), + + #[serde(rename = "snake_case")] + Files(PassportElementErrorFiles), + + #[serde(rename = "snake_case")] + TranslationFile(PassportElementErrorTranslationFile), + + #[serde(rename = "snake_case")] + TranslationFiles(PassportElementErrorTranslationFiles), + + #[serde(rename = "snake_case")] + Unspecified(PassportElementErrorUnspecified), +} + +/// Represents an issue in one of the data fields that was provided by the +/// user. +/// +/// The error is considered resolved when the field's value changes. +/// +/// [The official docs](https://core.telegram.org/bots/api#passportelementerrordatafield). +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct PassportElementErrorDataField { + /// The section of the user's Telegram Passport which has the error. + pub r#type: PassportElementErrorDataFieldType, + + /// Name of the data field which has the error. + pub field_name: String, + + /// Base64-encoded data hash. + pub data_hash: String, +} + +impl PassportElementErrorDataField { + pub fn new( + r#type: PassportElementErrorDataFieldType, + field_name: S1, + data_hash: S2, + ) -> Self + where + S1: Into, + S2: Into, + { + Self { r#type, field_name: field_name.into(), data_hash: data_hash.into() } + } + + pub fn r#type(mut self, val: PassportElementErrorDataFieldType) -> Self { + self.r#type = val; + self + } + + pub fn field_name(mut self, val: S) -> Self + where + S: Into, + { + self.field_name = val.into(); + self + } + + pub fn data_hash(mut self, val: S) -> Self + where + S: Into, + { + self.data_hash = val.into(); + self + } +} + +/// Represents an issue with the front side of a document. +/// +/// The error is considered resolved when the file with the front side of the +/// document changes. +/// +/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorfrontside). +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct PassportElementErrorFrontSide { + /// The section of the user's Telegram Passport which has the issue. + pub r#type: PassportElementErrorFrontSideType, + + /// Base64-encoded hash of the file with the front side of the + /// document. + pub file_hash: String, +} + +impl PassportElementErrorFrontSide { + pub fn new(r#type: PassportElementErrorFrontSideType, file_hash: S) -> Self + where + S: Into, + { + Self { r#type, file_hash: file_hash.into() } + } + + pub fn r#type(mut self, val: PassportElementErrorFrontSideType) -> Self { + self.r#type = val; + self + } + + pub fn file_hash(mut self, val: S) -> Self + where + S: Into, + { + self.file_hash = val.into(); + self + } +} + +/// Represents an issue with the reverse side of a document. +/// +/// The error is considered resolved when the file with reverse side of the +/// document changes. +/// +/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorreverseside). +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct PassportElementErrorReverseSide { + /// The section of the user's Telegram Passport which has the issue. + pub r#type: PassportElementErrorReverseSideType, + + //// Base64-encoded hash of the file with the reverse side of the + //// document. + pub file_hash: String, +} + +impl PassportElementErrorReverseSide { + pub fn new(r#type: PassportElementErrorReverseSideType, file_hash: S) -> Self + where + S: Into, + { + Self { r#type, file_hash: file_hash.into() } + } + + pub fn r#type(mut self, val: PassportElementErrorReverseSideType) -> Self { + self.r#type = val; + self + } + + pub fn file_hash(mut self, val: S) -> Self + where + S: Into, + { + self.file_hash = val.into(); + self + } +} + +//// Represents an issue with the selfie with a document. +// +/// The error is considered resolved when the file with the selfie changes. +/// +/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorselfie). +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct PassportElementErrorSelfie { + /// The section of the user's Telegram Passport which has the issue. + pub r#type: PassportElementErrorSelfieType, + + /// Base64-encoded hash of the file with the selfie. + pub file_hash: String, +} + +impl PassportElementErrorSelfie { + pub fn new(r#type: PassportElementErrorSelfieType, file_hash: S) -> Self + where + S: Into, + { + Self { r#type, file_hash: file_hash.into() } + } + + pub fn r#type(mut self, val: PassportElementErrorSelfieType) -> Self { + self.r#type = val; + self + } + + pub fn file_hash(mut self, val: S) -> Self + where + S: Into, + { + self.file_hash = val.into(); + self + } +} + +/// Represents an issue with a document scan. +/// +/// The error is considered resolved when the file with the document scan +/// changes. +/// +/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorfile). +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct PassportElementErrorFile { + /// The section of the user's Telegram Passport which has the issue. + pub r#type: PassportElementErrorFileType, + + /// Base64-encoded file hash. + pub file_hash: String, +} + +impl PassportElementErrorFile { + pub fn new(r#type: PassportElementErrorFileType, file_hash: S) -> Self + where + S: Into, + { + Self { r#type, file_hash: file_hash.into() } + } + + pub fn r#type(mut self, val: PassportElementErrorFileType) -> Self { + self.r#type = val; + self + } + + pub fn file_hash(mut self, val: S) -> Self + where + S: Into, + { + self.file_hash = val.into(); + self + } +} + +/// Represents an issue with a list of scans. +/// +/// The error is considered resolved when the list of files containing the scans +/// changes. +/// +/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorfiles). +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct PassportElementErrorFiles { + /// The section of the user's Telegram Passport which has the issue. + pub r#type: PassportElementErrorFilesType, + + /// List of base64-encoded file hashes. + pub file_hashes: Vec, +} + +impl PassportElementErrorFiles { + pub fn new(r#type: PassportElementErrorFilesType, file_hashes: S) -> Self + where + S: Into>, + { + Self { r#type, file_hashes: file_hashes.into() } + } + + pub fn r#type(mut self, val: PassportElementErrorFilesType) -> Self { + self.r#type = val; + self + } + + pub fn file_hashes(mut self, val: S) -> Self + where + S: Into>, + { + self.file_hashes = val.into(); + self + } +} + +/// Represents an issue with one of the files that constitute the +/// translation of a document. +/// +/// The error is considered resolved when the file changes. +/// +/// [The official docs](https://core.telegram.org/bots/api#passportelementerrortranslationfile). +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct PassportElementErrorTranslationFile { + /// Type of element of the user's Telegram Passport which has the + /// issue. + pub r#type: PassportElementErrorTranslationFileType, + + /// Base64-encoded file hash. + pub file_hash: String, +} + +impl PassportElementErrorTranslationFile { + pub fn new(r#type: PassportElementErrorTranslationFileType, file_hash: S) -> Self + where + S: Into, + { + Self { r#type, file_hash: file_hash.into() } + } + + pub fn r#type(mut self, val: PassportElementErrorTranslationFileType) -> Self { + self.r#type = val; + self + } + + pub fn file_hash(mut self, val: S) -> Self + where + S: Into, + { + self.file_hash = val.into(); + self + } +} + +/// Represents an issue with the translated version of a document. +/// +/// The error is considered resolved when a file with the document translation +/// change. +/// +/// [The official docs](https://core.telegram.org/bots/api#passportelementerrortranslationfiles). +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct PassportElementErrorTranslationFiles { + /// Type of element of the user's Telegram Passport which has the issue + pub r#type: PassportElementErrorTranslationFilesType, + + /// List of base64-encoded file hashes + pub file_hashes: Vec, +} + +impl PassportElementErrorTranslationFiles { + pub fn new(r#type: PassportElementErrorTranslationFilesType, file_hashes: S) -> Self + where + S: Into>, + { + Self { r#type, file_hashes: file_hashes.into() } + } + + pub fn r#type(mut self, val: PassportElementErrorTranslationFilesType) -> Self { + self.r#type = val; + self + } + + pub fn file_hashes(mut self, val: S) -> Self + where + S: Into>, + { + self.file_hashes = val.into(); + self + } +} + +/// Represents an issue in an unspecified place. +/// +/// The error is considered resolved when new data is added. +/// +/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorunspecified). +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct PassportElementErrorUnspecified { + /// Type of element of the user's Telegram Passport which has the + /// issue. + pub r#type: PassportElementErrorUnspecifiedType, + + /// Base64-encoded element hash. + pub element_hash: String, +} + +impl PassportElementErrorUnspecified { + pub fn new(r#type: PassportElementErrorUnspecifiedType, file_hash: S) -> Self + where + S: Into, + { + Self { r#type, element_hash: file_hash.into() } + } + + pub fn r#type(mut self, val: PassportElementErrorUnspecifiedType) -> Self { + self.r#type = val; + self + } + + pub fn element_hash(mut self, val: S) -> Self + where + S: Into, + { + self.element_hash = val.into(); + self + } +} + +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum PassportElementErrorDataFieldType { + PersonalDetails, + Passport, + DriverLicense, + IdentityCard, + InternalPassport, + Address, +} + +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum PassportElementErrorFrontSideType { + Passport, + DriverLicense, + IdentityCard, + InternalPassport, +} + +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum PassportElementErrorReverseSideType { + DriverLicense, + IdentityCard, +} + +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum PassportElementErrorSelfieType { + Passport, + DriverLicense, + IdentityCard, + InternalPassport, +} + +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum PassportElementErrorFileType { + UtilityBill, + BankStatement, + RentalAgreement, + PassportRegistration, + TemporaryRegistration, +} + +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum PassportElementErrorFilesType { + UtilityBill, + BankStatement, + RentalAgreement, + PassportRegistration, + TemporaryRegistration, +} + +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum PassportElementErrorTranslationFileType { + Passport, + DriverLicense, + IdentityCard, + InternalPassport, + UtilityBill, + BankStatement, + RentalAgreement, + PassportRegistration, + TemporaryRegistration, +} + +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum PassportElementErrorTranslationFilesType { + Passport, + DriverLicense, + IdentityCard, + InternalPassport, + UtilityBill, + BankStatement, + RentalAgreement, + PassportRegistration, + TemporaryRegistration, +} + +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum PassportElementErrorUnspecifiedType { + DataField, + FrontSide, + ReverseSide, + Selfie, + File, + Files, + TranslationFile, + TranslationFiles, + Unspecified, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serialize_data_field() { + let data = PassportElementError { + message: "This is an error message!".to_owned(), + kind: PassportElementErrorKind::DataField(PassportElementErrorDataField { + r#type: PassportElementErrorDataFieldType::InternalPassport, + field_name: "The field name".to_owned(), + data_hash: "This is a data hash".to_owned(), + }), + }; + + assert_eq!( + serde_json::to_string(&data).unwrap(), + r#"{"message":"This is an error message!","source":"data","type":"internal_passport","field_name":"The field name","data_hash":"This is a data hash"}"# + ); + } +} diff --git a/src/types/passport_file.rs b/src/types/passport_file.rs new file mode 100644 index 00000000..448d0c62 --- /dev/null +++ b/src/types/passport_file.rs @@ -0,0 +1,66 @@ +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. +/// +/// [The official docs](https://core.telegram.org/bots/api#passportfile). +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct PassportFile { + /// Identifier for this file. + pub file_id: String, + + /// Unique identifier for this file, which is supposed to be the same over + /// time and for different bots. Can't be used to download or reuse the + /// file. + pub file_unique_id: String, + + /// File size. + pub file_size: u64, + + /// Unix time when the file was uploaded. + pub file_date: u64, +} + +impl PassportFile { + pub fn new(file_id: S1, file_unique_id: S2, file_size: u64, file_date: u64) -> Self + where + S1: Into, + S2: Into, + { + Self { + file_id: file_id.into(), + file_unique_id: file_unique_id.into(), + file_size, + file_date, + } + } + + pub fn file_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_id = val.into(); + self + } + + pub fn file_unique_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_unique_id = val.into(); + self + } + + pub fn file_size(mut self, val: u64) -> Self { + self.file_size = val; + self + } + + pub fn file_date(mut self, val: u64) -> Self { + self.file_date = val; + self + } +} diff --git a/src/types/photo_size.rs b/src/types/photo_size.rs new file mode 100644 index 00000000..64ddfac7 --- /dev/null +++ b/src/types/photo_size.rs @@ -0,0 +1,94 @@ +use serde::{Deserialize, Serialize}; + +/// This object represents one size of a photo or a [file]/[sticker] thumbnail. +/// +/// [file]: crate::types::Document +/// [sticker]: crate::types::Sticker +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct PhotoSize { + /// Identifier for this file. + pub file_id: String, + + /// Unique identifier for this file, which is supposed to be the same over + /// time and for different bots. Can't be used to download or reuse the + /// file. + pub file_unique_id: String, + + /// Photo width. + pub width: i32, + + /// Photo height. + pub height: i32, + + /// File size. + pub file_size: Option, +} + +impl PhotoSize { + pub fn new(file_id: S1, file_unique_id: S2, width: i32, height: i32) -> Self + where + S1: Into, + S2: Into, + { + Self { + file_id: file_id.into(), + file_unique_id: file_unique_id.into(), + width, + height, + file_size: None, + } + } + + pub fn file_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_id = val.into(); + self + } + + pub fn file_unique_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_unique_id = val.into(); + self + } + + pub fn width(mut self, val: i32) -> Self { + self.width = val; + self + } + + pub fn height(mut self, val: i32) -> Self { + self.height = val; + self + } + + pub fn file_size(mut self, val: u32) -> Self { + self.file_size = Some(val); + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let json = r#"{"file_id":"id","file_unique_id":"","width":320,"height":320, + "file_size":3452}"#; + let expected = PhotoSize { + file_id: "id".to_string(), + file_unique_id: "".to_string(), + width: 320, + height: 320, + file_size: Some(3452), + }; + let actual = serde_json::from_str::(json).unwrap(); + assert_eq!(actual, expected); + } +} diff --git a/src/types/poll.rs b/src/types/poll.rs new file mode 100644 index 00000000..c3646e3b --- /dev/null +++ b/src/types/poll.rs @@ -0,0 +1,250 @@ +use crate::types::{MessageEntity, PollType}; +use serde::{Deserialize, Serialize}; + +/// This object contains information about a poll. +/// +/// [The official docs](https://core.telegram.org/bots/api#poll). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct Poll { + /// Unique poll identifier. + pub id: String, + + /// Poll question, 1-255 characters. + pub question: String, + + /// List of poll options. + pub options: Vec, + + /// `true`, if the poll is closed. + pub is_closed: bool, + + /// Total number of users that voted in the poll + pub total_voter_count: i32, + + /// True, if the poll is anonymous + pub is_anonymous: bool, + + /// Poll type, currently can be “regular” or “quiz” + #[serde(rename = "type")] + pub poll_type: PollType, + + /// True, if the poll allows multiple answers + pub allows_multiple_answers: bool, + + /// 0-based identifier of the correct answer option. Available only for + /// polls in the quiz mode, which are closed, or was sent (not + /// forwarded) by the bot or to the private chat with the bot. + pub correct_option_id: Option, + + /// Text that is shown when a user chooses an incorrect answer or taps on + /// the lamp icon in a quiz-style poll, 0-200 characters. + pub explanation: Option, + + /// Special entities like usernames, URLs, bot commands, etc. that appear in + /// the explanation. + pub explanation_entities: Option>, + + /// Amount of time in seconds the poll will be active after creation. + open_period: Option, + + /// Point in time (Unix timestamp) when the poll will be automatically + /// closed. + close_date: Option, +} + +impl Poll { + #[allow(clippy::too_many_arguments)] + pub fn new( + id: S1, + question: S2, + options: O, + is_closed: bool, + total_voter_count: i32, + is_anonymous: bool, + poll_type: PollType, + allows_multiple_answers: bool, + ) -> Self + where + S1: Into, + S2: Into, + O: Into>, + { + Self { + id: id.into(), + question: question.into(), + options: options.into(), + is_closed, + total_voter_count, + is_anonymous, + poll_type, + allows_multiple_answers, + correct_option_id: None, + explanation: None, + explanation_entities: None, + open_period: None, + close_date: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn question(mut self, val: S) -> Self + where + S: Into, + { + self.question = val.into(); + self + } + + pub fn options

(mut self, val: P) -> Self + where + P: Into>, + { + self.options = val.into(); + self + } + + #[allow(clippy::wrong_self_convention)] + pub fn is_closed(mut self, val: bool) -> Self { + self.is_closed = val; + self + } + + pub fn total_voter_count(mut self, val: i32) -> Self { + self.total_voter_count = val; + self + } + + #[allow(clippy::wrong_self_convention)] + pub fn is_anonymous(mut self, val: bool) -> Self { + self.is_anonymous = val; + self + } + + pub fn poll_type(mut self, val: PollType) -> Self { + self.poll_type = val; + self + } + + pub fn allows_multiple_answers(mut self, val: bool) -> Self { + self.allows_multiple_answers = val; + self + } + + pub fn correct_option_id(mut self, val: i32) -> Self { + self.correct_option_id = Some(val); + self + } + + pub fn explanation(mut self, val: S) -> Self + where + S: Into, + { + self.explanation = Some(val.into()); + self + } + + pub fn explanation_entities(mut self, val: S) -> Self + where + S: Into>, + { + self.explanation_entities = Some(val.into()); + self + } + + pub fn open_period(mut self, val: i32) -> Self { + self.open_period = Some(val); + self + } + + pub fn close_date(mut self, val: i32) -> Self { + self.close_date = Some(val); + self + } +} + +/// This object contains information about one answer option in a poll. +/// +/// [The official docs](https://core.telegram.org/bots/api#polloption). +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct PollOption { + /// Option text, 1-100 characters. + pub text: String, + + /// Number of users that voted for this option. + pub voter_count: i32, +} + +impl PollOption { + pub fn new(text: S, voter_count: i32) -> Self + where + S: Into, + { + Self { text: text.into(), voter_count } + } + + pub fn text(mut self, val: S) -> Self + where + S: Into, + { + self.text = val.into(); + self + } + + pub fn voter_count(mut self, val: i32) -> Self { + self.voter_count = val; + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "allows_multiple_answers": false, + "id": "5377643193141559299", + "is_anonymous": true, + "is_closed": false, + "options": [ + { + "text": "1", + "voter_count": 1 + }, + { + "text": "2", + "voter_count": 0 + }, + { + "text": "3", + "voter_count": 0 + }, + { + "text": "4", + "voter_count": 0 + }, + { + "text": "5", + "voter_count": 0 + } + ], + "question": "Rate me from 1 to 5.", + "total_voter_count": 1, + "type": "regular" + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/src/types/poll_answer.rs b/src/types/poll_answer.rs new file mode 100644 index 00000000..6ddb0b06 --- /dev/null +++ b/src/types/poll_answer.rs @@ -0,0 +1,48 @@ +use crate::types::User; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct PollAnswer { + /// Unique poll identifier. + pub poll_id: String, + + /// The user, who changed the answer to the poll. + pub user: User, + + /// 0-based identifiers of answer options, chosen by the user. + /// + /// May be empty if the user retracted their vote. + pub option_ids: Vec, +} + +impl PollAnswer { + pub fn new(poll_id: S, user: User, option_ids: O) -> Self + where + S: Into, + O: Into>, + { + Self { poll_id: poll_id.into(), user, option_ids: option_ids.into() } + } + + pub fn poll_id(mut self, val: S) -> Self + where + S: Into, + { + self.poll_id = val.into(); + self + } + + pub fn user(mut self, val: User) -> Self { + self.user = val; + self + } + + pub fn option_ids(mut self, val: S) -> Self + where + S: Into>, + { + self.option_ids = val.into(); + self + } +} diff --git a/src/types/poll_type.rs b/src/types/poll_type.rs new file mode 100644 index 00000000..0243f7c1 --- /dev/null +++ b/src/types/poll_type.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +#[non_exhaustive] +pub enum PollType { + Quiz, + Regular, +} diff --git a/src/types/pre_checkout_query.rs b/src/types/pre_checkout_query.rs new file mode 100644 index 00000000..87fd9707 --- /dev/null +++ b/src/types/pre_checkout_query.rs @@ -0,0 +1,108 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{Currency, OrderInfo, User}; + +/// This object contains information about an incoming pre-checkout query. +/// +/// [The official docs](https://core.telegram.org/bots/api#precheckoutquery). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct PreCheckoutQuery { + /// Unique query identifier. + pub id: String, + + /// User who sent the query. + pub from: User, + + /// Three-letter ISO 4217 [currency] code. + /// + /// [currency]: https://core.telegram.org/bots/payments#supported-currencies + pub currency: Currency, + + /// Total price in the _smallest units_ of the currency (integer, **not** + /// float/double). For example, for a price of `US$ 1.45` pass `amount = + /// 145`. See the exp parameter in [`currencies.json`], it shows the number + /// of digits past the decimal point for each currency (2 for the + /// majority of currencies). + /// + /// [`currencies.json`]: https://core.telegram.org/bots/payments/currencies.json + pub total_amount: i32, + + /// Bot specified invoice payload. + pub invoice_payload: String, + + /// Identifier of the shipping option chosen by the user. + pub shipping_option_id: Option, + + /// Order info provided by the user. + pub order_info: Option, +} + +impl PreCheckoutQuery { + pub fn new( + id: S1, + from: User, + currency: Currency, + total_amount: i32, + invoice_payload: S2, + ) -> Self + where + S1: Into, + S2: Into, + { + Self { + id: id.into(), + from, + currency, + total_amount, + invoice_payload: invoice_payload.into(), + shipping_option_id: None, + order_info: None, + } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn from(mut self, val: User) -> Self { + self.from = val; + self + } + + pub fn currency(mut self, val: Currency) -> Self { + self.currency = val; + self + } + + pub fn total_amount(mut self, val: i32) -> Self { + self.total_amount = val; + self + } + + pub fn invoice_payload(mut self, val: S) -> Self + where + S: Into, + { + self.invoice_payload = val.into(); + self + } + + pub fn shipping_option_id(mut self, val: S) -> Self + where + S: Into, + { + self.shipping_option_id = Some(val.into()); + self + } + + pub fn order_info(mut self, val: OrderInfo) -> Self { + self.order_info = Some(val); + self + } +} diff --git a/src/types/reply_keyboard_markup.rs b/src/types/reply_keyboard_markup.rs new file mode 100644 index 00000000..a8d5983e --- /dev/null +++ b/src/types/reply_keyboard_markup.rs @@ -0,0 +1,98 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::KeyboardButton; + +/// This object represents a [custom keyboard] with reply options (see +/// [Introduction to bots] for details and examples). +/// +/// [The official docs](https://core.telegram.org/bots/api#replykeyboardmarkup). +/// +/// [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, Default)] +#[non_exhaustive] +pub struct ReplyKeyboardMarkup { + /// Array of button rows, each represented by an Array of + /// [`KeyboardButton`] objects + /// + /// [`KeyboardButton`]: crate::types::KeyboardButton + pub keyboard: Vec>, + + /// Requests clients to resize the keyboard vertically for optimal fit + /// (e.g., make the keyboard smaller if there are just two rows of + /// buttons). Defaults to `false`, in which case the custom keyboard is + /// always of the same height as the app's standard keyboard. + pub resize_keyboard: Option, + + /// Requests clients to hide the keyboard as soon as it's been used. The + /// keyboard will still be available, but clients will automatically + /// display the usual letter-keyboard in the chat – the user can press a + /// special button in the input field to see the custom keyboard again. + /// Defaults to `false`. + pub one_time_keyboard: Option, + + /// Use this parameter if you want to show the keyboard to specific users + /// only. Targets: 1) users that are `@mentioned` in the `text` of the + /// [`Message`] object; 2) if the bot's message is a reply (has + /// `reply_to_message_id`), sender of the original message. + /// + /// Example: A user requests to change the bot‘s language, bot replies to + /// the request with a keyboard to select the new language. Other users + /// in the group don’t see the keyboard. + /// + /// [`Message`]: crate::types::Message + pub selective: Option, +} + +impl ReplyKeyboardMarkup { + pub fn new(keyboard: K1) -> Self + where + K1: Into>, + K2: Into>, + { + Self { + keyboard: keyboard.into().into_iter().map(Into::into).collect(), + resize_keyboard: None, + one_time_keyboard: None, + selective: None, + } + } + + 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 new file mode 100644 index 00000000..deb8351b --- /dev/null +++ b/src/types/reply_keyboard_remove.rs @@ -0,0 +1,52 @@ +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`]). +/// +/// [The official docs](https://core.telegram.org/bots/api#replykeyboardremove). +/// +/// [`ReplyKeyboardMarkup`]: crate::types::ReplyKeyboardMarkup +#[serde_with_macros::skip_serializing_none] +#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct ReplyKeyboardRemove { + /// Requests clients to remove the custom keyboard (user will not be able + /// to summon this keyboard; if you want to hide the keyboard from sight + /// but keep it accessible, use one_time_keyboard in + /// [`ReplyKeyboardMarkup`]). + /// + /// [`ReplyKeyboardMarkup`]: crate::types::ReplyKeyboardMarkup + pub remove_keyboard: True, + + /// Use this parameter if you want to remove the keyboard for specific + /// users only. Targets: 1) users that are `@mentioned` in the `text` of + /// the [`Message`] object; 2) if the bot's message is a reply (has + /// `reply_to_message_id`), sender of the original message. + /// + /// Example: A user votes in a poll, bot returns confirmation message in + /// reply to the vote and removes the keyboard for that user, while still + /// showing the keyboard with poll options to users who haven't voted yet. + /// + /// [`Message`]: crate::types::Message + pub selective: Option, +} + +impl ReplyKeyboardRemove { + pub fn new() -> Self { + Self::default() + } + + pub fn selective(mut self, val: T) -> Self + where + T: Into, + { + self.selective = Some(val.into()); + self + } +} diff --git a/src/types/reply_markup.rs b/src/types/reply_markup.rs new file mode 100644 index 00000000..4bddc56c --- /dev/null +++ b/src/types/reply_markup.rs @@ -0,0 +1,27 @@ +use derive_more::From; +use serde::{Deserialize, Serialize}; + +use crate::types::{ForceReply, InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove}; + +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, From)] +#[serde(untagged)] +#[non_exhaustive] +pub enum ReplyMarkup { + InlineKeyboardMarkup(InlineKeyboardMarkup), + ReplyKeyboardMarkup(ReplyKeyboardMarkup), + ReplyKeyboardRemove(ReplyKeyboardRemove), + ForceReply(ForceReply), +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn inline_keyboard_markup() { + let data = InlineKeyboardMarkup::default(); + let expected = ReplyMarkup::InlineKeyboardMarkup(data.clone()); + let actual: ReplyMarkup = data.into(); + assert_eq!(actual, expected) + } +} diff --git a/src/types/response_parameters.rs b/src/types/response_parameters.rs new file mode 100644 index 00000000..2f0fb43f --- /dev/null +++ b/src/types/response_parameters.rs @@ -0,0 +1,43 @@ +use serde::{Deserialize, Serialize}; + +/// Contains information about why a request was unsuccessful. +/// +/// [The official docs](https://core.telegram.org/bots/api#responseparameters). +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum ResponseParameters { + /// The group has been migrated to a supergroup with the specified + /// identifier. This number may be greater than 32 bits and some + /// programming languages may have difficulty/silent defects in + /// interpreting it. But it is smaller than 52 bits, so a signed 64 bit + /// integer or double-precision float type are safe for storing this + /// identifier. + MigrateToChatId(i64), + + /// In case of exceeding flood control, the number of seconds left to wait + /// before the request can be repeated. + RetryAfter(i32), +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn migrate_to_chat_id_deserialization() { + let expected = ResponseParameters::MigrateToChatId(123_456); + let actual: ResponseParameters = + serde_json::from_str(r#"{"migrate_to_chat_id":123456}"#).unwrap(); + + assert_eq!(expected, actual); + } + + #[test] + fn retry_after_deserialization() { + let expected = ResponseParameters::RetryAfter(123_456); + let actual: ResponseParameters = serde_json::from_str(r#"{"retry_after":123456}"#).unwrap(); + + assert_eq!(expected, actual); + } +} diff --git a/src/types/send_invoice.rs b/src/types/send_invoice.rs new file mode 100644 index 00000000..bc527b45 --- /dev/null +++ b/src/types/send_invoice.rs @@ -0,0 +1,229 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{ChatId, InlineKeyboardMarkup, LabeledPrice}; + +// TODO: missing docs +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct SendInvoice { + pub chat_id: ChatId, + pub title: String, + pub description: String, + pub payload: String, + pub provider_token: String, + pub start_parameter: String, + pub currency: String, + pub prices: Vec, + pub provider_data: Option, + pub photo_url: Option, + pub photo_size: Option, + pub photo_width: Option, + pub photo_height: Option, + pub need_name: Option, + pub need_phone_number: Option, + pub need_email: Option, + pub need_shipping_address: Option, + pub send_phone_number_to_provider: Option, + pub send_email_to_provider: Option, + pub is_flexible: Option, + pub disable_notification: Option, + pub reply_to_message_id: Option, + pub reply_markup: Option, +} + +impl SendInvoice { + #[allow(clippy::too_many_arguments)] + pub fn new( + chat_id: C, + title: S1, + description: S2, + payload: S3, + provider_token: S4, + start_parameter: S5, + currency: S6, + prices: P, + ) -> Self + where + C: Into, + S1: Into, + S2: Into, + S3: Into, + S4: Into, + S5: Into, + S6: Into, + P: Into>, + { + Self { + chat_id: chat_id.into(), + title: title.into(), + description: description.into(), + payload: payload.into(), + provider_token: provider_token.into(), + start_parameter: start_parameter.into(), + currency: currency.into(), + prices: prices.into(), + provider_data: None, + photo_url: None, + photo_size: None, + photo_width: None, + photo_height: None, + need_name: None, + need_phone_number: None, + need_email: None, + need_shipping_address: None, + send_phone_number_to_provider: None, + send_email_to_provider: None, + is_flexible: None, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + } + } + + pub fn chat_id(mut self, val: C) -> Self + where + C: Into, + { + self.chat_id = val.into(); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = val.into(); + self + } + + pub fn description(mut self, val: S) -> Self + where + S: Into, + { + self.description = val.into(); + self + } + + pub fn payload(mut self, val: S) -> Self + where + S: Into, + { + self.payload = val.into(); + self + } + + pub fn provider_token(mut self, val: S) -> Self + where + S: Into, + { + self.provider_token = val.into(); + self + } + + pub fn start_parameter(mut self, val: S) -> Self + where + S: Into, + { + self.start_parameter = val.into(); + self + } + + pub fn currency(mut self, val: S) -> Self + where + S: Into, + { + self.currency = val.into(); + self + } + + pub fn prices

(mut self, val: P) -> Self + where + P: Into>, + { + self.prices = val.into(); + self + } + + pub fn provider_data(mut self, val: S) -> Self + where + S: Into, + { + self.provider_data = Some(val.into()); + self + } + + pub fn photo_url(mut self, val: S) -> Self + where + S: Into, + { + self.photo_url = Some(val.into()); + self + } + + pub fn photo_size(mut self, val: i32) -> Self { + self.photo_size = Some(val); + self + } + + pub fn photo_width(mut self, val: i32) -> Self { + self.photo_width = Some(val); + self + } + + pub fn photo_height(mut self, val: i32) -> Self { + self.photo_height = Some(val); + self + } + + pub fn need_name(mut self, val: bool) -> Self { + self.need_name = Some(val); + self + } + + pub fn need_phone_number(mut self, val: bool) -> Self { + self.need_phone_number = Some(val); + self + } + + pub fn need_email(mut self, val: bool) -> Self { + self.need_email = Some(val); + self + } + + pub fn need_shipping_address(mut self, val: bool) -> Self { + self.need_shipping_address = Some(val); + self + } + + pub fn send_phone_number_to_provider(mut self, val: bool) -> Self { + self.send_phone_number_to_provider = Some(val); + self + } + + pub fn send_email_to_provider(mut self, val: bool) -> Self { + self.send_email_to_provider = Some(val); + self + } + + #[allow(clippy::wrong_self_convention)] + pub fn is_flexible(mut self, val: bool) -> Self { + self.is_flexible = Some(val); + self + } + + pub fn disable_notification(mut self, val: bool) -> Self { + self.disable_notification = Some(val); + self + } + + pub fn reply_to_message_id(mut self, value: i32) -> Self { + self.reply_to_message_id = Some(value); + self + } + + pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { + self.reply_markup = Some(val); + self + } +} diff --git a/src/types/shipping_address.rs b/src/types/shipping_address.rs new file mode 100644 index 00000000..3a0adb58 --- /dev/null +++ b/src/types/shipping_address.rs @@ -0,0 +1,100 @@ +use crate::types::CountryCode; +use serde::{Deserialize, Serialize}; + +/// This object represents a shipping address. +/// +/// [The official docs](https://core.telegram.org/bots/api#shippingaddress). +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct ShippingAddress { + /// ISO 3166-1 alpha-2 country code. + pub country_code: CountryCode, + + /// State, if applicable. + pub state: String, + + /// City. + pub city: String, + + /// First line for the address. + pub street_line1: String, + + /// Second line for the address. + pub street_line2: String, + + /// Address post code. + pub post_code: String, +} + +impl ShippingAddress { + pub fn new( + country_code: CountryCode, + + state: S1, + city: S2, + street_line1: S3, + street_line2: S4, + post_code: S5, + ) -> Self + where + S1: Into, + S2: Into, + S3: Into, + S4: Into, + S5: Into, + { + Self { + country_code, + state: state.into(), + city: city.into(), + street_line1: street_line1.into(), + street_line2: street_line2.into(), + post_code: post_code.into(), + } + } + + pub fn country_code(mut self, val: CountryCode) -> Self { + self.country_code = val; + self + } + + pub fn state(mut self, val: S) -> Self + where + S: Into, + { + self.state = val.into(); + self + } + + pub fn city(mut self, val: S) -> Self + where + S: Into, + { + self.city = val.into(); + self + } + + pub fn street_line1(mut self, val: S) -> Self + where + S: Into, + { + self.street_line1 = val.into(); + self + } + + pub fn street_line2(mut self, val: S) -> Self + where + S: Into, + { + self.street_line2 = val.into(); + self + } + + pub fn post_code(mut self, val: S) -> Self + where + S: Into, + { + self.post_code = val.into(); + self + } +} diff --git a/src/types/shipping_option.rs b/src/types/shipping_option.rs new file mode 100644 index 00000000..983c7eef --- /dev/null +++ b/src/types/shipping_option.rs @@ -0,0 +1,71 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::LabeledPrice; + +/// This object represents one shipping option. +/// +/// [The official docs](https://core.telegram.org/bots/api#shippingoption). +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct ShippingOption { + /// Shipping option identifier. + pub id: String, + + /// Option title. + pub title: String, + + /// List of price portions. + pub prices: Vec, +} + +impl ShippingOption { + pub fn new(id: S1, title: S2, prices: P) -> Self + where + S1: Into, + S2: Into, + P: Into>, + { + Self { id: id.into(), title: title.into(), prices: prices.into() } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = val.into(); + self + } + + pub fn prices

(mut self, val: P) -> Self + where + P: Into>, + { + self.prices = val.into(); + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serialize() { + let shipping_option = ShippingOption { + id: "0".to_string(), + title: "Option".to_string(), + prices: vec![LabeledPrice { label: "Label".to_string(), amount: 60 }], + }; + let expected = r#"{"id":"0","title":"Option","prices":[{"label":"Label","amount":60}]}"#; + let actual = serde_json::to_string(&shipping_option).unwrap(); + assert_eq!(actual, expected); + } +} diff --git a/src/types/shipping_query.rs b/src/types/shipping_query.rs new file mode 100644 index 00000000..bfdf7dc8 --- /dev/null +++ b/src/types/shipping_query.rs @@ -0,0 +1,63 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{ShippingAddress, User}; + +/// This object contains information about an incoming shipping query. +/// +/// [The official docs](https://core.telegram.org/bots/api#shippingquery). +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct ShippingQuery { + /// Unique query identifier. + pub id: String, + + /// User who sent the query. + pub from: User, + + /// Bot specified invoice payload. + pub invoice_payload: String, + + /// User specified shipping address. + pub shipping_address: ShippingAddress, +} + +impl ShippingQuery { + pub fn new( + id: S1, + from: User, + invoice_payload: S2, + shipping_address: ShippingAddress, + ) -> Self + where + S1: Into, + S2: Into, + { + Self { id: id.into(), from, invoice_payload: invoice_payload.into(), shipping_address } + } + + pub fn id(mut self, val: S) -> Self + where + S: Into, + { + self.id = val.into(); + self + } + + pub fn from(mut self, val: User) -> Self { + self.from = val; + self + } + + pub fn invoice_payload(mut self, val: S) -> Self + where + S: Into, + { + self.invoice_payload = val.into(); + self + } + + pub fn shipping_address(mut self, val: ShippingAddress) -> Self { + self.shipping_address = val; + self + } +} diff --git a/src/types/sticker.rs b/src/types/sticker.rs new file mode 100644 index 00000000..d133f1cc --- /dev/null +++ b/src/types/sticker.rs @@ -0,0 +1,135 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{MaskPosition, PhotoSize}; + +/// This object represents a sticker. +/// +/// [The official docs](https://core.telegram.org/bots/api#sticker). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct Sticker { + /// Identifier for this file. + pub file_id: String, + + /// Unique identifier for this file, which is supposed to be the same over + /// time and for different bots. Can't be used to download or reuse the + /// file. + pub file_unique_id: String, + + /// Sticker width. + pub width: u16, + + /// Sticker height. + pub height: u16, + + /// `true`, if the sticker is [animated]. + /// + /// [animated]: https://telegram.org/blog/animated-stickers + pub is_animated: bool, + + /// Sticker thumbnail in the .webp or .jpg format. + pub thumb: Option, + + /// Emoji associated with the sticker. + pub emoji: Option, + + /// Name of the sticker set to which the sticker belongs. + pub set_name: Option, + + /// For mask stickers, the position where the mask should be placed. + pub mask_position: Option, + + /// File size. + pub file_size: Option, +} + +impl Sticker { + pub fn new( + file_id: S1, + file_unique_id: S2, + width: u16, + height: u16, + is_animated: bool, + ) -> Self + where + S1: Into, + S2: Into, + { + Self { + file_id: file_id.into(), + file_unique_id: file_unique_id.into(), + width, + height, + is_animated, + thumb: None, + emoji: None, + set_name: None, + mask_position: None, + file_size: None, + } + } + + pub fn file_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_id = val.into(); + self + } + + pub fn file_unique_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_unique_id = val.into(); + self + } + + pub fn height(mut self, val: u16) -> Self { + self.height = val; + self + } + + pub fn width(mut self, val: u16) -> Self { + self.width = val; + self + } + + #[allow(clippy::wrong_self_convention)] + pub fn is_animated(mut self, val: bool) -> Self { + self.is_animated = val; + self + } + + pub fn thumb(mut self, val: PhotoSize) -> Self { + self.thumb = Some(val); + self + } + + pub fn emoji(mut self, val: S) -> Self + where + S: Into, + { + self.emoji = Some(val.into()); + self + } + + pub fn set_name(mut self, val: S) -> Self + where + S: Into, + { + self.set_name = Some(val.into()); + self + } + + pub fn mask_position(mut self, val: MaskPosition) -> Self { + self.mask_position = Some(val); + self + } + + pub fn file_size(mut self, val: u32) -> Self { + self.file_size = Some(val); + self + } +} diff --git a/src/types/sticker_set.rs b/src/types/sticker_set.rs new file mode 100644 index 00000000..0c617205 --- /dev/null +++ b/src/types/sticker_set.rs @@ -0,0 +1,89 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{PhotoSize, Sticker}; + +/// This object represents a sticker set. +/// +/// [The official docs](https://core.telegram.org/bots/api#stickerset). +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct StickerSet { + /// Sticker set name. + pub name: String, + + /// Sticker set title. + pub title: String, + + /// `true`, if the sticker set contains [animated stickers]. + /// + /// [animates stickers]: https://telegram.org/blog/animated-stickers + pub is_animated: bool, + + /// `true`, if the sticker set contains masks. + pub contains_masks: bool, + + /// List of all set stickers. + pub stickers: Vec, + + /// Sticker set thumbnail in the .WEBP or .TGS format. + thumb: Option, +} + +impl StickerSet { + pub fn new( + name: S1, + title: S2, + is_animated: bool, + contains_masks: bool, + stickers: St, + ) -> Self + where + S1: Into, + S2: Into, + St: Into>, + { + Self { + name: name.into(), + title: title.into(), + is_animated, + contains_masks, + stickers: stickers.into(), + thumb: None, + } + } + + pub fn name(mut self, val: S) -> Self + where + S: Into, + { + self.name = val.into(); + self + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = val.into(); + self + } + + #[allow(clippy::wrong_self_convention)] + pub fn is_animated(mut self, val: bool) -> Self { + self.is_animated = val; + self + } + + pub fn contains_masks(mut self, val: bool) -> Self { + self.contains_masks = val; + self + } + + pub fn stickers(mut self, val: S) -> Self + where + S: Into>, + { + self.stickers = val.into(); + self + } +} diff --git a/src/types/sticker_type.rs b/src/types/sticker_type.rs new file mode 100644 index 00000000..b80added --- /dev/null +++ b/src/types/sticker_type.rs @@ -0,0 +1,26 @@ +use crate::types::InputFile; + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[non_exhaustive] +pub enum StickerType { + /// PNG image with the sticker, must be up to 512 kilobytes in size, + /// dimensions must not exceed 512px, and either width or height must be + /// exactly 512px. + /// + /// Pass [`InputFile::File`] to send a file that exists on + /// the Telegram servers (recommended), pass an [`InputFile::Url`] for + /// Telegram to get a .webp file from the Internet, or upload a new one + /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. + /// + /// [`InputFile::File`]: crate::types::InputFile::File + /// [`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 + Png(InputFile), + + /// TGS animation with the sticker, uploaded using multipart/form-data. + /// + /// See https://core.telegram.org/animated_stickers#technical-requirements for technical requirements + Tgs(InputFile), +} diff --git a/src/types/successful_payment.rs b/src/types/successful_payment.rs new file mode 100644 index 00000000..439dbc23 --- /dev/null +++ b/src/types/successful_payment.rs @@ -0,0 +1,112 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{Currency, OrderInfo}; + +/// This object contains basic information about a successful payment. +/// +/// [The official docs](https://core.telegram.org/bots/api#successfulpayment). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct SuccessfulPayment { + /// Three-letter ISO 4217 [currency] code. + /// + /// [currency]: https://core.telegram.org/bots/payments#supported-currencies + pub currency: Currency, + + /// Total price in the smallest units of the currency (integer, not + /// float/double). For example, for a price of `US$ 1.45` pass `amount = + /// 145`. See the exp parameter in [`currencies.json`], it shows the + /// number of digits past the decimal point for each currency (2 for + /// the majority of currencies). + /// + /// [`currencies.json`]: https://core.telegram.org/bots/payments/currencies.json + pub total_amount: i32, + + /// Bot specified invoice payload. + pub invoice_payload: String, + + /// Identifier of the shipping option chosen by the user. + pub shipping_option_id: Option, + + /// Order info provided by the user. + pub order_info: Option, + + /// Telegram payment identifier. + pub telegram_payment_charge_id: String, + + /// Provider payment identifier. + pub provider_payment_charge_id: String, +} + +impl SuccessfulPayment { + pub fn new( + currency: Currency, + total_amount: i32, + invoice_payload: S1, + telegram_payment_charge_id: S2, + provider_payment_charge_id: S3, + ) -> Self + where + S1: Into, + S2: Into, + S3: Into, + { + Self { + currency, + total_amount, + invoice_payload: invoice_payload.into(), + shipping_option_id: None, + order_info: None, + telegram_payment_charge_id: telegram_payment_charge_id.into(), + provider_payment_charge_id: provider_payment_charge_id.into(), + } + } + + pub fn currency(mut self, val: Currency) -> Self { + self.currency = val; + self + } + + pub fn total_amount(mut self, val: i32) -> Self { + self.total_amount = val; + self + } + + pub fn invoice_payload(mut self, val: S) -> Self + where + S: Into, + { + self.invoice_payload = val.into(); + self + } + + pub fn shipping_option_id(mut self, val: S) -> Self + where + S: Into, + { + self.shipping_option_id = Some(val.into()); + self + } + + pub fn order_info(mut self, val: OrderInfo) -> Self { + self.order_info = Some(val); + self + } + + pub fn telegram_payment_charge_id(mut self, val: S) -> Self + where + S: Into, + { + self.telegram_payment_charge_id = val.into(); + self + } + + pub fn provider_payment_charge_id(mut self, val: S) -> Self + where + S: Into, + { + self.provider_payment_charge_id = val.into(); + self + } +} diff --git a/src/types/target_message.rs b/src/types/target_message.rs new file mode 100644 index 00000000..0c0d8cc1 --- /dev/null +++ b/src/types/target_message.rs @@ -0,0 +1,17 @@ +use crate::types::ChatId; + +use serde::{Deserialize, Serialize}; + +/// A message in chat or inline message. +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum TargetMessage { + Common { chat_id: ChatId, message_id: i32 }, + Inline { inline_message_id: String }, +} + +impl From for TargetMessage { + fn from(inline_message_id: String) -> Self { + Self::Inline { inline_message_id } + } +} diff --git a/src/types/unit_false.rs b/src/types/unit_false.rs new file mode 100644 index 00000000..bde6410b --- /dev/null +++ b/src/types/unit_false.rs @@ -0,0 +1,76 @@ +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; + +impl std::convert::TryFrom for False { + type Error = (); + + fn try_from(value: bool) -> Result { + match value { + true => Err(()), + false => Ok(False), + } + } +} + +impl<'de> Deserialize<'de> for False { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_bool(FalseVisitor) + } +} + +struct FalseVisitor; + +impl<'de> Visitor<'de> for FalseVisitor { + type Value = False; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "bool, equal to `false`") + } + + fn visit_bool(self, value: bool) -> Result + where + E: serde::de::Error, + { + match value { + true => Err(E::custom("expected `false`, found `true`")), + false => Ok(False), + } + } +} + +impl Serialize for False { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bool(false) + } +} + +#[cfg(test)] +mod tests { + use serde_json::{from_str, to_string}; + + use super::False; + + #[test] + fn unit_false_de() { + let json = "false"; + let expected = False; + let actual = from_str(json).unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn unit_false_se() { + let actual = to_string(&False).unwrap(); + let expected = "false"; + assert_eq!(expected, actual); + } +} diff --git a/src/types/unit_true.rs b/src/types/unit_true.rs new file mode 100644 index 00000000..cd71e5c2 --- /dev/null +++ b/src/types/unit_true.rs @@ -0,0 +1,79 @@ +use serde::{ + de::{self, Deserialize, Deserializer, Visitor}, + ser::{Serialize, Serializer}, +}; + +/// A type that is always true. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Default)] +pub struct True; + +impl std::convert::TryFrom for True { + type Error = (); + + fn try_from(value: bool) -> Result { + match value { + true => Ok(True), + false => Err(()), + } + } +} + +impl<'de> Deserialize<'de> for True { + fn deserialize(des: D) -> Result + where + D: Deserializer<'de>, + { + des.deserialize_bool(TrueVisitor) + } +} + +struct TrueVisitor; + +impl<'de> Visitor<'de> for TrueVisitor { + type Value = True; + + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "bool, equal to `true`") + } + + fn visit_bool(self, value: bool) -> Result + where + E: de::Error, + { + match value { + true => Ok(True), + false => Err(E::custom("expected `true`, found `false`")), + } + } +} + +impl Serialize for True { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bool(true) + } +} + +#[cfg(test)] +mod tests { + use serde_json::{from_str, to_string}; + + use super::True; + + #[test] + fn unit_true_de() { + let json = "true"; + let expected = True; + let actual = from_str(json).unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn unit_true_se() { + let actual = to_string(&True).unwrap(); + let expected = "true"; + assert_eq!(expected, actual); + } +} diff --git a/src/types/update.rs b/src/types/update.rs new file mode 100644 index 00000000..13514a39 --- /dev/null +++ b/src/types/update.rs @@ -0,0 +1,299 @@ +#![allow(clippy::large_enum_variant)] + +use serde::{Deserialize, Serialize}; + +use crate::types::{ + CallbackQuery, Chat, ChosenInlineResult, InlineQuery, Message, Poll, PollAnswer, + PreCheckoutQuery, ShippingQuery, User, +}; + +/// This [object] represents an incoming update. +/// +/// [The official docs](https://core.telegram.org/bots/api#update). +/// +/// [object]: https://core.telegram.org/bots/api#available-types +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct Update { + /// The update‘s unique identifier. Update identifiers start from a certain + /// positive number and increase sequentially. This ID becomes especially + /// handy if you’re using [Webhooks], since it allows you to ignore + /// repeated updates or to restore the correct update sequence, should + /// they get out of order. If there are no new updates for at least a + /// week, then identifier of the next update will be chosen randomly + /// instead of sequentially. + /// + /// [Webhooks]: crate::Bot::set_webhook + #[serde(rename = "update_id")] + pub id: i32, + + #[serde(flatten)] + pub kind: UpdateKind, +} + +impl Update { + pub fn new(id: i32, kind: UpdateKind) -> Self { + Self { id, kind } + } + + pub fn id(mut self, val: i32) -> Self { + self.id = val; + self + } + + pub fn kind(mut self, val: UpdateKind) -> Self { + self.kind = val; + self + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum UpdateKind { + /// New incoming message of any kind — text, photo, sticker, etc. + Message(Message), + + /// New version of a message that is known to the bot and was edited. + EditedMessage(Message), + + /// New incoming channel post of any kind — text, photo, sticker, etc. + ChannelPost(Message), + + /// New version of a channel post that is known to the bot and was edited. + EditedChannelPost(Message), + + /// New incoming [inline] query. + /// + /// [inline]: https://core.telegram.org/bots/api#inline-mode + InlineQuery(InlineQuery), + + /// The result of an [inline] query that was chosen by a user and sent to + /// their chat partner. Please see our documentation on the [feedback + /// collecting] for details on how to enable these updates for your bot. + /// + /// [inline]: https://core.telegram.org/bots/api#inline-mode + /// [feedback collecting]: https://core.telegram.org/bots/inline#collecting-feedback + ChosenInlineResult(ChosenInlineResult), + + /// New incoming callback query. + CallbackQuery(CallbackQuery), + + /// New incoming shipping query. Only for invoices with flexible price. + ShippingQuery(ShippingQuery), + + /// New incoming pre-checkout query. Contains full information about + /// checkout. + PreCheckoutQuery(PreCheckoutQuery), + + /// New poll state. Bots receive only updates about stopped polls and + /// polls, which are sent by the bot. + Poll(Poll), + + /// A user changed their answer in a non-anonymous poll. Bots receive new + /// votes only in polls that were sent by the bot itself. + PollAnswer(PollAnswer), +} + +impl Update { + pub fn user(&self) -> Option<&User> { + match &self.kind { + 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), + UpdateKind::ShippingQuery(query) => Some(&query.from), + UpdateKind::PreCheckoutQuery(query) => Some(&query.from), + UpdateKind::PollAnswer(answer) => Some(&answer.user), + _ => None, + } + } + + pub fn chat(&self) -> Option<&Chat> { + match &self.kind { + UpdateKind::Message(m) => Some(&m.chat), + UpdateKind::EditedMessage(m) => Some(&m.chat), + UpdateKind::ChannelPost(p) => Some(&p.chat), + UpdateKind::EditedChannelPost(p) => Some(&p.chat), + UpdateKind::CallbackQuery(q) => Some(&q.message.as_ref()?.chat), + _ => None, + } + } +} + +#[cfg(test)] +mod test { + use crate::types::{ + Chat, ChatKind, ChatPrivate, ForwardKind, ForwardOrigin, MediaKind, MediaText, Message, + MessageCommon, MessageKind, Update, UpdateKind, User, + }; + + // TODO: more tests for deserialization + #[test] + fn message() { + let json = r#"{ + "update_id":892252934, + "message":{ + "message_id":6557, + "from":{ + "id":218485655, + "is_bot": false, + "first_name":"Waffle", + "username":"WaffleLapkin", + "language_code":"en" + }, + "chat":{ + "id":218485655, + "first_name":"Waffle", + "username":"WaffleLapkin", + "type":"private" + }, + "date":1569518342, + "text":"hello there" + } + }"#; + + let expected = Update { + id: 892_252_934, + kind: UpdateKind::Message(Message { + via_bot: None, + id: 6557, + date: 1_569_518_342, + chat: Chat { + id: 218_485_655, + kind: ChatKind::Private(ChatPrivate { + type_: (), + username: Some(String::from("WaffleLapkin")), + first_name: Some(String::from("Waffle")), + last_name: None, + }), + photo: None, + }, + kind: MessageKind::Common(MessageCommon { + from: Some(User { + id: 218_485_655, + is_bot: false, + first_name: String::from("Waffle"), + last_name: None, + username: Some(String::from("WaffleLapkin")), + language_code: Some(String::from("en")), + }), + forward_kind: ForwardKind::Origin(ForwardOrigin { reply_to_message: None }), + edit_date: None, + media_kind: MediaKind::Text(MediaText { + text: String::from("hello there"), + entities: vec![], + }), + reply_markup: None, + }), + }), + }; + + 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()); + } + + #[test] + fn pinned_message_works() { + let json = r#"{ + "message": { + "chat": { + "id": -1001276785818, + "title": "teloxide dev", + "type": "supergroup", + "username": "teloxide_dev" + }, + "date": 1582134655, + "from": { + "first_name": "Hirrolot", + "id": 408258968, + "is_bot": false, + "username": "hirrolot" + }, + "message_id": 20225, + "pinned_message": { + "chat": { + "id": -1001276785818, + "title": "teloxide dev", + "type": "supergroup", + "username": "teloxide_dev" + }, + "date": 1582134643, + "from": { + "first_name": "Hirrolot", + "id": 408258968, + "is_bot": false, + "username": "hirrolot" + }, + "message_id": 20224, + "text": "Faster than a bullet" + } + }, + "update_id": 845402291 +}"#; + + serde_json::from_str::(json).unwrap(); + } + + #[test] + fn dice_works() { + let json = r#" + { + "message": { + "chat": { + "id": -1001276785818, + "title": "bla bla bla chat", + "type": "supergroup", + "username": "teloxide_dev" + }, + "date": 1596014550, + "dice": { + "emoji": "🎲", + "value": 2 + }, + "from": { + "first_name": "Hirrolot", + "id": 408258968, + "is_bot": false, + "language_code": "en", + "username": "hirrolot" + }, + "message_id": 35410 + }, + "update_id": 573255266 +} + "#; + + serde_json::from_str::(json).unwrap(); + } +} diff --git a/src/types/user.rs b/src/types/user.rs new file mode 100644 index 00000000..d24f5caa --- /dev/null +++ b/src/types/user.rs @@ -0,0 +1,132 @@ +use serde::{Deserialize, Serialize}; + +/// This object represents a Telegram user or bot. +/// +/// [The official docs](https://core.telegram.org/bots/api#user). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct User { + /// Unique identifier for this user or bot. + pub id: i32, + + /// `true`, if this user is a bot. + pub is_bot: bool, + + /// User‘s or bot’s first name. + pub first_name: String, + + /// User‘s or bot’s last name. + pub last_name: Option, + + /// User‘s or bot’s username. + pub username: Option, + + /// [IETF language tag] of the user's language. + /// + /// [IETF language tag]: https://en.wikipedia.org/wiki/IETF_language_tag + pub language_code: Option, +} + +impl User { + pub fn new(id: i32, is_bot: bool, first_name: S) -> Self + where + S: Into, + { + Self { + id, + is_bot, + first_name: first_name.into(), + last_name: None, + username: None, + language_code: None, + } + } + + pub fn id(mut self, val: i32) -> Self { + self.id = val; + self + } + + #[allow(clippy::wrong_self_convention)] + pub fn is_bot(mut self, val: bool) -> Self { + self.is_bot = val; + self + } + + pub fn first_name(mut self, val: S) -> Self + where + S: Into, + { + self.first_name = val.into(); + self + } + + pub fn last_name(mut self, val: S) -> Self + where + S: Into, + { + self.last_name = Some(val.into()); + self + } + + pub fn username(mut self, val: S) -> Self + where + S: Into, + { + self.username = Some(val.into()); + self + } + + pub fn language_code(mut self, val: S) -> Self + where + S: Into, + { + self.language_code = Some(val.into()); + self + } +} + +impl User { + pub fn full_name(&self) -> String { + match &self.last_name { + Some(last_name) => (format!("{0} {1}", self.first_name, last_name)), + None => self.first_name.clone(), + } + } + + pub fn mention(&self) -> Option { + Some(format!("@{}", self.username.as_ref()?)) + } + + pub fn url(&self) -> reqwest::Url { + reqwest::Url::parse(format!("tg://user/?id={}", self.id).as_str()).unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let json = r#"{ + "id":12345, + "is_bot":false, + "first_name":"firstName", + "last_name":"lastName", + "username":"Username", + "language_code":"ru" + }"#; + let expected = User { + id: 12345, + is_bot: false, + first_name: "firstName".to_string(), + last_name: Some("lastName".to_string()), + username: Some("Username".to_string()), + language_code: Some(String::from("ru")), + }; + let actual = serde_json::from_str::(&json).unwrap(); + assert_eq!(actual, expected) + } +} diff --git a/src/types/user_profile_photos.rs b/src/types/user_profile_photos.rs new file mode 100644 index 00000000..26d8a934 --- /dev/null +++ b/src/types/user_profile_photos.rs @@ -0,0 +1,40 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::PhotoSize; + +/// This object represent a user's profile pictures. +/// +/// [The official docs](https://core.telegram.org/bots/api#userprofilephotos). +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct UserProfilePhotos { + /// Total number of profile pictures the target user has. + pub total_count: u32, + + /// Requested profile pictures (in up to 4 sizes each). + pub photos: Vec>, +} + +impl UserProfilePhotos { + pub fn new(total_count: u32, photos: P1) -> Self + where + P1: Into>, + P2: Into>, + { + Self { total_count, photos: photos.into().into_iter().map(Into::into).collect() } + } + + pub fn total_count(mut self, val: u32) -> Self { + self.total_count = val; + self + } + + pub fn photos(mut self, val: P1) -> Self + where + P1: Into>, + P2: Into>, + { + self.photos = val.into().into_iter().map(Into::into).collect(); + self + } +} diff --git a/src/types/venue.rs b/src/types/venue.rs new file mode 100644 index 00000000..3a8ff38a --- /dev/null +++ b/src/types/venue.rs @@ -0,0 +1,74 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::Location; + +/// This object represents a venue. +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct Venue { + /// Venue location. + pub location: Location, + + /// Name of the venue. + pub title: String, + + /// Address of the venue. + pub address: String, + + /// Foursquare identifier of the venue. + pub foursquare_id: Option, + + /// Foursquare type of the venue. (For example, + /// `arts_entertainment/default`, `arts_entertainment/aquarium` or + /// `food/icecream`.) + pub foursquare_type: Option, +} + +impl Venue { + pub fn new(location: Location, title: S1, address: S2) -> Self + where + S1: Into, + S2: Into, + { + Self { + location, + title: title.into(), + address: address.into(), + foursquare_id: None, + foursquare_type: None, + } + } + + pub fn title(mut self, val: S) -> Self + where + S: Into, + { + self.title = val.into(); + self + } + + pub fn address(mut self, val: S) -> Self + where + S: Into, + { + self.address = val.into(); + self + } + + pub fn foursquare_id(mut self, val: S) -> Self + where + S: Into, + { + self.foursquare_id = Some(val.into()); + self + } + + pub fn foursquare_type(mut self, val: S) -> Self + where + S: Into, + { + self.foursquare_type = Some(val.into()); + self + } +} diff --git a/src/types/video.rs b/src/types/video.rs new file mode 100644 index 00000000..84de086a --- /dev/null +++ b/src/types/video.rs @@ -0,0 +1,108 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{MimeWrapper, PhotoSize}; + +/// This object represents a video file. +/// +/// [The official docs](https://core.telegram.org/bots/api#video). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct Video { + /// Identifier for this file. + pub file_id: String, + + /// Unique identifier for this file, which is supposed to be the same over + /// time and for different bots. Can't be used to download or reuse the + /// file. + pub file_unique_id: String, + + /// Video width as defined by sender. + pub width: u32, + + /// Video height as defined by sender. + pub height: u32, + + /// Duration of the video in seconds as defined by sender. + pub duration: u32, + + /// Video thumbnail. + pub thumb: Option, + + /// Mime type of a file as defined by sender. + pub mime_type: Option, + + /// File size. + pub file_size: Option, +} + +impl Video { + pub fn new( + file_id: S1, + file_unique_id: S2, + width: u32, + height: u32, + duration: u32, + ) -> Self + where + S1: Into, + S2: Into, + { + Self { + file_id: file_id.into(), + file_unique_id: file_unique_id.into(), + width, + height, + duration, + thumb: None, + mime_type: None, + file_size: None, + } + } + + pub fn file_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_id = val.into(); + self + } + + pub fn file_unique_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_unique_id = val.into(); + self + } + + pub fn width(mut self, val: u32) -> Self { + self.width = val; + self + } + + pub fn height(mut self, val: u32) -> Self { + self.height = val; + self + } + + pub fn duration(mut self, val: u32) -> Self { + self.duration = val; + self + } + + pub fn thumb(mut self, val: PhotoSize) -> Self { + self.thumb = Some(val); + self + } + + pub fn mime_type(mut self, val: MimeWrapper) -> Self { + self.mime_type = Some(val); + self + } + + pub fn file_size(mut self, val: u32) -> Self { + self.file_size = Some(val); + self + } +} diff --git a/src/types/video_note.rs b/src/types/video_note.rs new file mode 100644 index 00000000..467ace0e --- /dev/null +++ b/src/types/video_note.rs @@ -0,0 +1,89 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::PhotoSize; + +/// This object represents a [video message] (available in Telegram apps as of +/// [v.4.0]). +/// +/// [The official docs](https://core.telegram.org/bots/api#videonote). +/// +/// [video message]: https://telegram.org/blog/video-messages-and-telescope +/// [v4.0]: https://telegram.org/blog/video-messages-and-telescope +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct VideoNote { + /// Identifier for this file. + pub file_id: String, + + /// Unique identifier for this file, which is supposed to be the same over + /// time and for different bots. Can't be used to download or reuse the + /// file. + pub file_unique_id: String, + + /// Video width and height (diameter of the video message) as defined by + /// sender. + pub length: u32, + + /// Duration of the video in seconds as defined by sender. + pub duration: u32, + + /// Video thumbnail. + pub thumb: Option, + + /// File size. + pub file_size: Option, +} + +impl VideoNote { + pub fn new(file_id: S1, file_unique_id: S2, length: u32, duration: u32) -> Self + where + S1: Into, + S2: Into, + { + Self { + file_id: file_id.into(), + file_unique_id: file_unique_id.into(), + length, + duration, + thumb: None, + file_size: None, + } + } + + pub fn file_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_id = val.into(); + self + } + + pub fn file_unique_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_unique_id = val.into(); + self + } + + pub fn length(mut self, val: u32) -> Self { + self.length = val; + self + } + + pub fn duration(mut self, val: u32) -> Self { + self.duration = val; + self + } + + pub fn thumb(mut self, val: PhotoSize) -> Self { + self.thumb = Some(val); + self + } + + pub fn file_size(mut self, val: u32) -> Self { + self.file_size = Some(val); + self + } +} diff --git a/src/types/voice.rs b/src/types/voice.rs new file mode 100644 index 00000000..bd522cbb --- /dev/null +++ b/src/types/voice.rs @@ -0,0 +1,74 @@ +use crate::types::MimeWrapper; +use serde::{Deserialize, Serialize}; + +/// This object represents a voice note. +/// +/// [The official docs](https://core.telegram.org/bots/api#voice). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct Voice { + /// Identifier for this file. + pub file_id: String, + + /// Unique identifier for this file, which is supposed to be the same over + /// time and for different bots. Can't be used to download or reuse the + /// file. + pub file_unique_id: String, + + /// Duration of the audio in seconds as defined by sender. + pub duration: u32, + + /// MIME type of the file as defined by sender. + pub mime_type: Option, + + /// File size. + pub file_size: Option, +} + +impl Voice { + pub fn new(file_id: S1, file_unique_id: S2, duration: u32) -> Self + where + S1: Into, + S2: Into, + { + Self { + file_id: file_id.into(), + file_unique_id: file_unique_id.into(), + duration, + mime_type: None, + file_size: None, + } + } + + pub fn file_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_id = val.into(); + self + } + + pub fn file_unique_id(mut self, val: S) -> Self + where + S: Into, + { + self.file_unique_id = val.into(); + self + } + + pub fn duration(mut self, val: u32) -> Self { + self.duration = val; + self + } + + pub fn mime_type(mut self, val: MimeWrapper) -> Self { + self.mime_type = Some(val); + self + } + + pub fn file_size(mut self, val: u64) -> Self { + self.file_size = Some(val); + self + } +} diff --git a/src/types/webhook_info.rs b/src/types/webhook_info.rs new file mode 100644 index 00000000..782a588d --- /dev/null +++ b/src/types/webhook_info.rs @@ -0,0 +1,98 @@ +use serde::{Deserialize, Serialize}; + +/// Contains information about the current status of a webhook. +/// +/// [The official docs](https://core.telegram.org/bots/api#webhookinfo). +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct WebhookInfo { + /// Webhook URL, may be empty if webhook is not set up. + pub url: String, + + /// `true`, if a custom certificate was provided for webhook certificate + /// checks. + pub has_custom_certificate: bool, + + /// Number of updates awaiting delivery. + pub pending_update_count: u32, + + /// Unix time for the most recent error that happened when trying to + /// deliver an update via webhook. + pub last_error_date: Option, + + /// Error message in human-readable format for the most recent error that + /// happened when trying to deliver an update via webhook. + pub last_error_message: Option, + + /// Maximum allowed number of simultaneous HTTPS connections to the webhook + /// for update delivery. + pub max_connections: Option, + + /// A list of update types the bot is subscribed to. Defaults to all update + /// types. + pub allowed_updates: Option>, +} + +impl WebhookInfo { + pub fn new(url: S, has_custom_certificate: bool, pending_update_count: u32) -> Self + where + S: Into, + { + Self { + url: url.into(), + has_custom_certificate, + pending_update_count, + last_error_date: None, + + last_error_message: None, + max_connections: None, + allowed_updates: None, + } + } + + pub fn url(mut self, val: S) -> Self + where + S: Into, + { + self.url = val.into(); + self + } + + pub fn has_custom_certificate(mut self, val: bool) -> Self { + self.has_custom_certificate = val; + self + } + + pub fn pending_update_count(mut self, val: u32) -> Self { + self.pending_update_count = val; + self + } + + pub fn last_error_date(mut self, val: u64) -> Self { + self.last_error_date = Some(val); + self + } + + pub fn last_error_message(mut self, val: S) -> Self + where + S: Into, + { + self.last_error_message = Some(val.into()); + self + } + + pub fn max_connections(mut self, val: u32) -> Self { + self.max_connections = Some(val); + self + } + + pub fn allowed_updates(mut self, val: A) -> Self + where + A: Into>, + S: Into, + { + self.allowed_updates = Some(val.into().into_iter().map(Into::into).collect()); + self + } +} From 95cfc0530f7460077d57ea62cbecd9d5466a6688 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Wed, 12 Aug 2020 20:17:26 +0300 Subject: [PATCH 074/755] Add badges to readme --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8026aea2..e6ad7315 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ # teloxide-core -Core part of `teloxide` library. \ No newline at end of file +[![CI status](https://github.com/teloxide/teloxide-core/workflows/Continuous%20integration/badge.svg)](https://github.com/teloxide/teloxide-core/actions) +[![documentation (master)](https://img.shields.io/badge/docs-master-blue)](https://teloxide-core.netlify.com) +[![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) + +Core part of `teloxide` library. From 842a084abf4500cd8155c920523306ad942b139d Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 12 Aug 2020 20:23:18 +0300 Subject: [PATCH 075/755] replace `teloxide` with `teloxide_core` in doc tests --- src/bot/download.rs | 6 +++--- src/types/inline_keyboard_button.rs | 2 +- src/types/inline_keyboard_markup.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bot/download.rs b/src/bot/download.rs index 7035cfe7..3890d79e 100644 --- a/src/bot/download.rs +++ b/src/bot/download.rs @@ -17,10 +17,10 @@ impl Bot { /// ## Examples /// /// ```no_run - /// use teloxide::types::File as TgFile; + /// use teloxide_core::types::File as TgFile; /// use tokio::fs::File; - /// # use teloxide::RequestError; - /// use teloxide::{requests::Request, Bot}; + /// # use teloxide_core::RequestError; + /// use teloxide_core::{requests::Request, Bot}; /// /// # async fn run() -> Result<(), Box> { /// let bot = Bot::new("TOKEN"); diff --git a/src/types/inline_keyboard_button.rs b/src/types/inline_keyboard_button.rs index 12497463..fd108692 100644 --- a/src/types/inline_keyboard_button.rs +++ b/src/types/inline_keyboard_button.rs @@ -101,7 +101,7 @@ pub enum InlineKeyboardButtonKind { /// /// # Examples /// ``` -/// use teloxide::types::InlineKeyboardButton; +/// use teloxide_core::types::InlineKeyboardButton; /// /// let url_button = InlineKeyboardButton::url("Text".to_string(), "http://url.com".to_string()); /// ``` diff --git a/src/types/inline_keyboard_markup.rs b/src/types/inline_keyboard_markup.rs index b6884a96..06893959 100644 --- a/src/types/inline_keyboard_markup.rs +++ b/src/types/inline_keyboard_markup.rs @@ -25,7 +25,7 @@ pub struct InlineKeyboardMarkup { /// /// # Examples /// ``` -/// use teloxide::types::{InlineKeyboardButton, InlineKeyboardMarkup}; +/// use teloxide_core::types::{InlineKeyboardButton, InlineKeyboardMarkup}; /// /// let url_button = InlineKeyboardButton::url("text".to_string(), "http://url.com".to_string()); /// let keyboard = InlineKeyboardMarkup::default().append_row(vec![url_button]); From 06f123710ec632b91188691a532ef7c2505952cc Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 15 Aug 2020 23:52:08 +0300 Subject: [PATCH 076/755] add `RequestError::Io` to wrap I/O errors Add `RequestError::Io(std::io::Error)` to wrap I/O errors those may happen while reading files to upload them to telegram --- src/errors.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/errors.rs b/src/errors.rs index 466cc501..83fc2d42 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,3 +1,5 @@ +use std::io; + use derive_more::From; use reqwest::StatusCode; use serde::Deserialize; @@ -34,6 +36,9 @@ pub enum RequestError { #[error("An error while parsing JSON: {0}")] InvalidJson(#[source] serde_json::Error), + + #[error("An I/O error: {0}")] + Io(#[source] io::Error), } /// A kind of an API error. From b303958afdb0828fb0f4970aed2f7290dcd8231e Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 16 Aug 2020 00:07:19 +0300 Subject: [PATCH 077/755] implement serde multipart serializer Serde multipart serializer can be used to serialize requests into multipart/form-data without need for copy-paste and boilerplate --- Cargo.toml | 3 +- src/lib.rs | 4 + src/local_macros.rs | 38 + src/serde_multipart/mod.rs | 28 + src/serde_multipart/serializers.rs | 678 ++++++++++++++++++ src/serde_multipart/unserializers.rs | 83 +++ src/serde_multipart/unserializers/bytes.rs | 145 ++++ .../unserializers/input_file.rs | 173 +++++ src/serde_multipart/unserializers/string.rs | 61 ++ src/types/input_file.rs | 53 +- 10 files changed, 1245 insertions(+), 21 deletions(-) create mode 100644 src/local_macros.rs create mode 100644 src/serde_multipart/mod.rs create mode 100644 src/serde_multipart/serializers.rs create mode 100644 src/serde_multipart/unserializers.rs create mode 100644 src/serde_multipart/unserializers/bytes.rs create mode 100644 src/serde_multipart/unserializers/input_file.rs create mode 100644 src/serde_multipart/unserializers/string.rs diff --git a/Cargo.toml b/Cargo.toml index 48a598d0..22724491 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ authors = [ [dependencies] futures = "0.3.5" -tokio = { version = "0.2.21", features = ["fs", "stream"] } +tokio = { version = "0.2.21", features = ["fs", "stream", "rt-threaded", "macros"] } tokio-util = "0.3.1" bytes = "0.5.5" async-trait = "0.1.36" @@ -26,6 +26,7 @@ reqwest = { version = "0.10.6", features = ["json", "stream"] } serde = { version = "1.0.114", features = ["derive"] } serde_json = "1.0.55" serde_with_macros = "1.1.0" +uuid = { version = "0.8.1", features = ["v4"] } # for attaching input files derive_more = "0.99.9" mime = "0.3.16" diff --git a/src/lib.rs b/src/lib.rs index 27ddfcd6..8ea7ef81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,9 @@ #![forbid(unsafe_code)] //#![deny(missing_docs)] +#[macro_use] +mod local_macros; // internal helper macros + pub use self::{ bot::{Bot, BotBuilder}, errors::{ApiErrorKind, DownloadError, KnownApiErrorKind, RequestError}, @@ -25,6 +28,7 @@ mod errors; // implementation details mod net; +mod serde_multipart; /// Constructs a client from the `TELOXIDE_PROXY` environmental variable. /// diff --git a/src/local_macros.rs b/src/local_macros.rs new file mode 100644 index 00000000..9dfa940d --- /dev/null +++ b/src/local_macros.rs @@ -0,0 +1,38 @@ +#[macro_use] +macro_rules! forward_to_unsuported_ty { + ( + supported: $supported:expr; + simple { $( $method:ident $arg:ty )* } + unit { $( $method1:ident $ty:expr )* } + compound { + $( $method2:ident $( <$T:ident: ?Sized + Serialize> )? ( $( $args:tt )* ) -> $ret:ty => $message:expr )* + } + ) => { + $( + fn $method(self, _: $arg) -> Result { + Err(Self::Error::UnsupportedType { + ty: stringify!($arg), + supported: $supported, + }) + } + )+ + + $( + fn $method1(self) -> Result { + Err(Self::Error::UnsupportedType { + ty: $ty, + supported: $supported, + }) + } + )+ + + $( + fn $method2 $( <$T: ?Sized + Serialize> )? (self, $( $args )*) -> Result<$ret, Self::Error> { + Err(Self::Error::UnsupportedType { + ty: $message, + supported: $supported, + }) + } + )+ + }; +} diff --git a/src/serde_multipart/mod.rs b/src/serde_multipart/mod.rs new file mode 100644 index 00000000..814dc906 --- /dev/null +++ b/src/serde_multipart/mod.rs @@ -0,0 +1,28 @@ +//! Module for serializing into `multipart/form-data` +//! ([`reqwest::multipart::Form`]) +//! +//! [`reqwest::multipart::Form`]: reqwest::multipart::Form +//! +//! ## How it works +//! +//! You better not know... + +mod serializers; +mod unserializers; + +use std::future::Future; + +use reqwest::multipart::Form; +use serde::Serialize; + +use serializers::MultipartTopLvlSerializer; + +pub(crate) use serializers::Error; + +/// Serializes given value into [`Form`] +/// +/// [`Form`]: reqwest::multipart::Form +pub(crate) fn to_form(val: &T) -> impl Future> { + let fut = val.serialize(MultipartTopLvlSerializer {}); + async { Ok(fut?.await?) } +} diff --git a/src/serde_multipart/serializers.rs b/src/serde_multipart/serializers.rs new file mode 100644 index 00000000..7e45e4f9 --- /dev/null +++ b/src/serde_multipart/serializers.rs @@ -0,0 +1,678 @@ +use crate::{ + serde_multipart::unserializers::{InputFileUnserializer, StringUnserializer}, + types::InputFile, +}; +use futures::{ + future::{ready, BoxFuture}, + stream::FuturesUnordered, + FutureExt, StreamExt, TryStreamExt, +}; +use reqwest::multipart::{Form, Part}; +use serde::{ + ser, + ser::{Impossible, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant}, + Serialize, Serializer, +}; +use std::{fmt, fmt::Display, io}; + +#[derive(Debug, derive_more::From)] +pub(crate) enum Error { + Custom(String), + TopLevelNotStruct, + InputFileUnserializerError(crate::serde_multipart::unserializers::UnserializerError), + Io(std::io::Error), + Json(serde_json::Error), +} + +impl ser::Error for Error { + fn custom(msg: T) -> Self + where + T: Display, + { + Self::Custom(msg.to_string()) + } +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Custom(s) => write!(f, "Custom serde error: {}", s), + Self::TopLevelNotStruct => write!(f, "Multipart supports only structs at top level"), + Self::InputFileUnserializerError(inner) => { + write!(f, "Error while unserializing input file: {}", inner) + } + Self::Io(inner) => write!(f, "Io error: {}", inner), + Self::Json(inner) => write!(f, "Json (de)serialization error: {}", inner), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + +pub(crate) struct MultipartTopLvlSerializer {} + +impl Serializer for MultipartTopLvlSerializer { + type Ok = ::Ok; + type Error = Error; + type SerializeSeq = Impossible; + type SerializeTuple = Impossible; + type SerializeTupleStruct = Impossible; + type SerializeTupleVariant = Impossible; + type SerializeMap = MultipartMapSerializer; // for `serde(flatten)` (e.g.: in CreateNewStickerSet) + type SerializeStruct = MultipartSerializer; + type SerializeStructVariant = Impossible; + + fn serialize_bool(self, _: bool) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_i8(self, _: i8) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_i16(self, _: i16) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_i32(self, _: i32) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_i64(self, _: i64) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_u8(self, _: u8) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_u16(self, _: u16) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_u32(self, _: u32) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_u64(self, _: u64) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_f32(self, _: f32) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_f64(self, _: f64) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_char(self, _: char) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_str(self, _: &str) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_bytes(self, _: &[u8]) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_none(self) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_some(self, _: &T) -> Result + where + T: Serialize, + { + Err(Error::TopLevelNotStruct) + } + + fn serialize_unit(self) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_unit_struct(self, _: &'static str) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_unit_variant( + self, + _: &'static str, + _: u32, + _: &'static str, + ) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_newtype_struct( + self, + _: &'static str, + _: &T, + ) -> Result + where + T: Serialize, + { + Err(Error::TopLevelNotStruct) + } + + fn serialize_newtype_variant( + self, + _: &'static str, + _: u32, + _: &'static str, + _: &T, + ) -> Result + where + T: Serialize, + { + Err(Error::TopLevelNotStruct) + } + + fn serialize_seq(self, _: Option) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_tuple(self, _: usize) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_tuple_struct( + self, + _: &'static str, + _: usize, + ) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_tuple_variant( + self, + _: &'static str, + _: u32, + _: &'static str, + _: usize, + ) -> Result { + Err(Error::TopLevelNotStruct) + } + + fn serialize_map(self, _: Option) -> Result { + Ok(MultipartMapSerializer { parts: vec![], files: vec![], key: None }) + } + + fn serialize_struct( + self, + _: &'static str, + _: usize, + ) -> Result { + Ok(MultipartSerializer::new()) + } + + fn serialize_struct_variant( + self, + _: &'static str, + _: u32, + _: &'static str, + _: usize, + ) -> Result { + Err(Error::TopLevelNotStruct) + } +} + +pub(crate) struct MultipartSerializer { + parts: Vec<(&'static str, Part)>, // TODO: Array vecs + files: Vec<(String, InputFile)>, +} + +impl MultipartSerializer { + fn new() -> Self { + Self { parts: Vec::new(), files: vec![] } + } +} + +impl SerializeStruct for MultipartSerializer { + type Ok = BoxFuture<'static, io::Result

>; // impl Future> + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> + where + T: Serialize, + { + let (part, file) = value.serialize(PartSerializer {})?; + self.parts.push((key, part)); + self.files.extend(file); + + Ok(()) + } + + fn end(self) -> Result { + let form = + self.parts.into_iter().fold(Form::new(), |acc, (key, value)| acc.part(key, value)); + + if self.files.is_empty() { + //Ok(Either::Left(ready(Ok(form)))) + Ok(Box::pin(ready(Ok(form)))) + } else { + let fut = self + .files + .into_iter() + .map(|(k, f)| f.into_part().map(move |p| (k, p))) + .collect::>() + .map(Ok) + .try_fold(form, |acc, (k, p)| async { Ok(acc.part(k, p?)) }); + + //Ok(Either::Right(fut)) + Ok(Box::pin(fut)) + } + } +} + +pub(crate) struct MultipartMapSerializer { + parts: Vec<(String, Part)>, // TODO: Array vecs + files: Vec<(String, InputFile)>, + key: Option, +} + +impl SerializeMap for MultipartMapSerializer { + type Ok = ::Ok; + type Error = Error; + + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + self.key = Some(key.serialize(StringUnserializer)?); + Ok(()) + } + + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + let key = self.key.take().unwrap(); + + let (part, file) = value.serialize(PartSerializer {})?; + self.parts.push((key, part)); + self.files.extend(file); + + Ok(()) + } + + fn end(self) -> Result { + let form = + self.parts.into_iter().fold(Form::new(), |acc, (key, value)| acc.part(key, value)); + + if self.files.is_empty() { + //Ok(Either::Left(ready(Ok(form)))) + Ok(Box::pin(ready(Ok(form)))) + } else { + let fut = self + .files + .into_iter() + .map(|(k, f)| f.into_part().map(move |p| (k, p))) + .collect::>() + .map(Ok) + .try_fold(form, |acc, (k, p)| async { Ok(acc.part(k, p?)) }); + + //Ok(Either::Right(fut)) + Ok(Box::pin(fut)) + } + } +} + +struct PartSerializer {} + +impl Serializer for PartSerializer { + type Ok = (Part, Vec<(String, InputFile)>); + type Error = Error; + type SerializeSeq = InnerPartSerializer; + type SerializeTuple = Impossible; + type SerializeTupleStruct = Impossible; + type SerializeTupleVariant = Impossible; + type SerializeMap = Impossible; + type SerializeStruct = PartSerializerStruct; + type SerializeStructVariant = PartFromFile; + + fn serialize_bool(self, v: bool) -> Result { + Ok((Part::text(v.to_string()), Vec::new())) + } + + fn serialize_i8(self, _: i8) -> Result { + unimplemented!() + } + + fn serialize_i16(self, _: i16) -> Result { + unimplemented!() + } + + fn serialize_i32(self, v: i32) -> Result { + Ok((Part::text(v.to_string()), Vec::new())) + } + + fn serialize_i64(self, v: i64) -> Result { + Ok((Part::text(v.to_string()), Vec::new())) + } + + fn serialize_u8(self, _: u8) -> Result { + unimplemented!() + } + + fn serialize_u16(self, _: u16) -> Result { + unimplemented!() + } + + fn serialize_u32(self, _: u32) -> Result { + unimplemented!() + } + + fn serialize_u64(self, _: u64) -> Result { + unimplemented!() + } + + fn serialize_f32(self, _: f32) -> Result { + unimplemented!() + } + + fn serialize_f64(self, _: f64) -> Result { + unimplemented!() + } + + fn serialize_char(self, _: char) -> Result { + unimplemented!() + } + + fn serialize_str(self, v: &str) -> Result { + Ok((Part::text(v.to_owned()), Vec::new())) + } + + fn serialize_bytes(self, _: &[u8]) -> Result { + unimplemented!() + } + + fn serialize_none(self) -> Result { + unimplemented!() + } + + fn serialize_some(self, value: &T) -> Result + where + T: Serialize, + { + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + unimplemented!() + } + + fn serialize_unit_struct(self, _: &'static str) -> Result { + unimplemented!() + } + + fn serialize_unit_variant( + self, + _: &'static str, + _: u32, + variant_name: &'static str, + ) -> Result { + Ok((Part::text(variant_name), Vec::new())) + } + + fn serialize_newtype_struct( + self, + _: &'static str, + _: &T, + ) -> Result + where + T: Serialize, + { + unimplemented!() + } + + fn serialize_newtype_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: Serialize, + { + let file: InputFile = InputFileUnserializer::NotMem.serialize_newtype_variant( + name, + variant_index, + variant, + value, + )?; + + match file { + f @ InputFile::Memory { .. } | f @ InputFile::File(_) => { + let uuid = uuid::Uuid::new_v4().to_string(); + let part = Part::text(format!("attach://{}", uuid)); + Ok((part, vec![(uuid, f)])) + } + InputFile::FileId(s) | InputFile::Url(s) => Ok((Part::text(s), Vec::new())), + } + } + + fn serialize_seq(self, _: Option) -> Result { + Ok(Self::SerializeSeq { array_json_parts: vec![], files: vec![] }) + } + + fn serialize_tuple(self, _: usize) -> Result { + unimplemented!() + } + + fn serialize_tuple_struct( + self, + _: &'static str, + _: usize, + ) -> Result { + unimplemented!() + } + + fn serialize_tuple_variant( + self, + _: &'static str, + _: u32, + _: &'static str, + _: usize, + ) -> Result { + unimplemented!() + } + + fn serialize_map(self, _: Option) -> Result { + unimplemented!() + } + + fn serialize_struct( + self, + name: &'static str, + len: usize, + ) -> Result { + let mut ser = serde_json::Serializer::new(Vec::new()); + ser.serialize_struct(name, len)?; + Ok(PartSerializerStruct( + ser, // TODO: capcity + serde_json::ser::State::First, + Vec::new(), + )) + } + + fn serialize_struct_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + Ok(PartFromFile { + inner: InputFileUnserializer::memory().serialize_struct_variant( + name, + variant_index, + variant, + len, + )?, + }) + } +} + +struct PartFromFile { + inner: InputFileUnserializer, +} + +impl SerializeStructVariant for PartFromFile { + type Ok = (Part, Vec<(String, InputFile)>); + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> + where + T: Serialize, + { + self.inner.serialize_field(key, value).map_err(Error::InputFileUnserializerError) + } + + fn end(self) -> Result { + let file = self.inner.end()?; + + // TODO: to method + match file { + f @ InputFile::Memory { .. } | f @ InputFile::File(_) => { + let uuid = uuid::Uuid::new_v4().to_string(); + let part = Part::text(format!("attach://{}", uuid)); + + Ok((part, vec![(uuid, f)])) + } + InputFile::FileId(s) | InputFile::Url(s) => Ok((Part::text(s), vec![])), + } + } +} + +struct InnerPartSerializer { + array_json_parts: Vec, // using value is such a workaround :| + files: Vec<(String, InputFile)>, +} + +impl SerializeSeq for InnerPartSerializer { + type Ok = (Part, Vec<(String, InputFile)>); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + // NOTE: this is probably highly inefficient (especially for ::Memory), + // but at least it works + let mut value = serde_json::to_value(value)?; + let file: InputFile = serde_json::from_value(value["media"].take())?; + + match file { + f @ InputFile::Memory { .. } | f @ InputFile::File(_) => { + let uuid = uuid::Uuid::new_v4().to_string(); + value["media"] = serde_json::Value::String(format!("attach://{}", uuid)); + self.files.push((uuid, f)); + } + InputFile::FileId(s) | InputFile::Url(s) => { + value["media"] = serde_json::Value::String(s); + } + } + + self.array_json_parts.push(value); + + Ok(()) + } + + fn end(self) -> Result { + let s = serde_json::to_string(&self.array_json_parts)?; + Ok((Part::text(s), self.files)) + } +} + +struct PartSerializerStruct( + serde_json::Serializer>, + serde_json::ser::State, + Vec<(String, InputFile)>, +); + +impl SerializeStruct for PartSerializerStruct { + type Ok = (Part, Vec<(String, InputFile)>); + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> + where + T: Serialize, + { + let state = match self.1 { + serde_json::ser::State::Empty => serde_json::ser::State::Empty, + serde_json::ser::State::First => serde_json::ser::State::First, + serde_json::ser::State::Rest => serde_json::ser::State::Rest, + }; + let mut ser = serde_json::ser::Compound::Map { ser: &mut self.0, state }; + + // special case media (required for `edit_message_media` to work) + if key == "media" { + let file = value.serialize(InputFileUnserializer::NotMem)?; + + match file { + f @ InputFile::Memory { .. } | f @ InputFile::File(_) => { + let uuid = uuid::Uuid::new_v4().to_string(); + let attach = format!("attach://{}", uuid); + + SerializeStruct::serialize_field(&mut ser, key, attach.as_str())?; + self.1 = get_state(ser); + + self.2.push((uuid, f)); + } + InputFile::FileId(s) | InputFile::Url(s) => { + SerializeStruct::serialize_field(&mut ser, key, &s)?; + self.1 = get_state(ser) + } + } + } else { + SerializeStruct::serialize_field(&mut ser, key, value)?; + self.1 = get_state(ser); + } + + Ok(()) + } + + fn end(mut self) -> Result { + let state = match self.1 { + serde_json::ser::State::Empty => serde_json::ser::State::Empty, + serde_json::ser::State::First => serde_json::ser::State::First, + serde_json::ser::State::Rest => serde_json::ser::State::Rest, + }; + let ser = serde_json::ser::Compound::Map { ser: &mut self.0, state }; + SerializeStruct::end(ser)?; + + let json = self.0.into_inner(); + Ok((Part::bytes(json), self.2)) + } +} + +fn get_state( + compound: serde_json::ser::Compound, serde_json::ser::CompactFormatter>, +) -> serde_json::ser::State { + // Compound may have more variants under some serde_json features + #[allow(unreachable_patterns)] + match compound { + serde_json::ser::Compound::Map { ser: _, state } => state, + _ => unreachable!(), + } +} diff --git a/src/serde_multipart/unserializers.rs b/src/serde_multipart/unserializers.rs new file mode 100644 index 00000000..b4b4775f --- /dev/null +++ b/src/serde_multipart/unserializers.rs @@ -0,0 +1,83 @@ +mod bytes; +mod input_file; +mod string; + +pub(crate) use input_file::InputFileUnserializer; +pub(crate) use string::StringUnserializer; + +use std::fmt::{self, Display}; + +use serde::ser; + +#[derive(Debug, PartialEq, Eq)] +pub enum UnserializerError { + Custom(String), + UnsupportedType { ty: &'static str, supported: &'static str }, + UnexpectedField { name: &'static str, expected: &'static [&'static str] }, + UnexpectedVariant { name: &'static str, expected: &'static [&'static str] }, + WrongLen { len: usize, expected: usize }, +} + +impl ser::Error for UnserializerError { + fn custom(msg: T) -> Self + where + T: Display, + { + Self::Custom(msg.to_string()) + } +} + +impl Display for UnserializerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::UnexpectedField { name, expected } => write!( + f, + "Unexpected field: `{}`, expected field(s): `{}`", + name, + expected.join(", ") + ), + Self::Custom(s) => write!(f, "Custom serde error: {}", s), + Self::UnsupportedType { ty, supported } => { + write!(f, "Unsupported type: `{}`, supported type(s): `{}`", ty, supported) + } + Self::UnexpectedVariant { name, expected } => write!( + f, + "Unexpected variant: `{}`, expected variants(s): `{}`", + name, + expected.join(", ") + ), + Self::WrongLen { len, expected } => { + write!(f, "Wrong len: `{}`, expected `{}`", len, expected) + } + } + } +} + +impl std::error::Error for UnserializerError {} + +#[test] +fn test() { + use crate::serde_multipart::unserializers::string::StringUnserializer; + use serde::Serialize; + + use crate::{ + serde_multipart::unserializers::input_file::InputFileUnserializer, types::InputFile, + }; + use std::borrow::Cow; + + let value = String::from("test"); + assert_eq!(value.serialize(StringUnserializer), Ok(value)); + + let value = InputFile::Url(String::from("url")); + assert_eq!(value.serialize(InputFileUnserializer::NotMem), Ok(value)); + + let value = InputFile::FileId(String::from("file_id")); + assert_eq!(value.serialize(InputFileUnserializer::NotMem), Ok(value)); + + let value = + InputFile::Memory { file_name: String::from("name"), data: Cow::Owned(vec![1, 2, 3]) }; + assert_eq!(value.serialize(InputFileUnserializer::memory()), Ok(value)); + + let value = InputFile::File("a/b/c".into()); + assert_eq!(value.serialize(InputFileUnserializer::NotMem), Ok(value)); +} diff --git a/src/serde_multipart/unserializers/bytes.rs b/src/serde_multipart/unserializers/bytes.rs new file mode 100644 index 00000000..182b9da7 --- /dev/null +++ b/src/serde_multipart/unserializers/bytes.rs @@ -0,0 +1,145 @@ +use crate::serde_multipart::unserializers::UnserializerError; +use serde::{ + ser::{Impossible, SerializeSeq}, + Serialize, Serializer, +}; + +#[derive(Default)] +pub(crate) struct BytesUnserializer(Vec); + +impl Serializer for BytesUnserializer { + type Ok = Vec; + type Error = UnserializerError; + + type SerializeSeq = Self; + type SerializeTuple = Impossible, UnserializerError>; + type SerializeTupleStruct = Impossible, UnserializerError>; + type SerializeTupleVariant = Impossible, UnserializerError>; + type SerializeMap = Impossible, UnserializerError>; + type SerializeStruct = Impossible, UnserializerError>; + type SerializeStructVariant = Impossible, UnserializerError>; + + fn serialize_bytes(self, v: &[u8]) -> Result { + Ok(v.to_owned()) + } + + fn serialize_seq(mut self, len: Option) -> Result { + if let Some(len) = len { + self.0.reserve_exact(len); + } + Ok(self) + } + + forward_to_unsuported_ty! { + supported: "&[u8], Vec, Cow<[u8]>"; + simple { + serialize_bool bool + serialize_i8 i8 + serialize_i16 i16 + serialize_i32 i32 + serialize_i64 i64 + serialize_u8 u8 + serialize_u16 u16 + serialize_u32 u32 + serialize_u64 u64 + serialize_f32 f32 + serialize_f64 f64 + serialize_char char + serialize_str &str + } + unit { + serialize_none "None" + serialize_unit "unit" + } + compound { + serialize_some(_: &T) -> Self::Ok => "Some(_)" + serialize_unit_struct(_: &'static str) -> Self::Ok => "unit struct" + serialize_unit_variant(_: &'static str, _: u32, _: &'static str) -> Self::Ok => "unit variant" + serialize_newtype_struct(_: &'static str, _: &T) -> Self::Ok => "newtype struct" + serialize_newtype_variant(_: &'static str, _: u32, _: &'static str, _: &T) -> Self::Ok => "newtype variant" + serialize_tuple(_: usize) -> Self::SerializeTuple => "tuple" + serialize_tuple_struct(_: &'static str, _: usize) -> Self::SerializeTupleStruct => "tuple struct" + serialize_tuple_variant(_: &'static str, _: u32, _: &'static str, _: usize) -> Self::SerializeTupleVariant => "tuple variant" + serialize_map(_: Option) -> Self::SerializeMap => "map" + serialize_struct(_: &'static str, _: usize) -> Self::SerializeStruct => "struct" + serialize_struct_variant(_: &'static str, _: u32, _: &'static str, _: usize) -> Self::SerializeStructVariant => "struct variant" + } + } +} + +impl SerializeSeq for BytesUnserializer { + type Ok = Vec; + type Error = UnserializerError; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + value.serialize(BytesUnserializerPush(&mut self.0)) + } + + fn end(self) -> Result { + Ok(self.0) + } +} + +pub(crate) struct BytesUnserializerPush<'a>(&'a mut Vec); + +impl Serializer for BytesUnserializerPush<'_> { + type Ok = (); + type Error = UnserializerError; + + type SerializeSeq = Impossible<(), UnserializerError>; + type SerializeTuple = Impossible<(), UnserializerError>; + type SerializeTupleStruct = Impossible<(), UnserializerError>; + type SerializeTupleVariant = Impossible<(), UnserializerError>; + type SerializeMap = Impossible<(), UnserializerError>; + type SerializeStruct = Impossible<(), UnserializerError>; + type SerializeStructVariant = Impossible<(), UnserializerError>; + + fn serialize_u8(self, v: u8) -> Result { + self.0.push(v); + Ok(()) + } + + fn serialize_bytes(self, v: &[u8]) -> Result { + self.0.extend_from_slice(v); + Ok(()) + } + + forward_to_unsuported_ty! { + supported: "&[u8], Vec, Cow<[u8]>"; + simple { + serialize_bool bool + serialize_i8 i8 + serialize_i16 i16 + serialize_i32 i32 + serialize_i64 i64 + serialize_u16 u16 + serialize_u32 u32 + serialize_u64 u64 + serialize_f32 f32 + serialize_f64 f64 + serialize_char char + serialize_str &str + } + unit { + serialize_none "None" + serialize_unit "unit" + } + compound { + serialize_some(_: &T) -> Self::Ok => "Some(_)" + serialize_unit_struct(_: &'static str) -> Self::Ok => "unit struct" + serialize_unit_variant(_: &'static str, _: u32, _: &'static str) -> Self::Ok => "unit variant" + serialize_newtype_struct(_: &'static str, _: &T) -> Self::Ok => "newtype struct" + serialize_newtype_variant(_: &'static str, _: u32, _: &'static str, _: &T) -> Self::Ok => "newtype variant" + serialize_seq(_: Option) -> Self::SerializeSeq => "sequence" + serialize_tuple(_: usize) -> Self::SerializeTuple => "tuple" + serialize_tuple_struct(_: &'static str, _: usize) -> Self::SerializeTupleStruct => "tuple struct" + serialize_tuple_variant(_: &'static str, _: u32, _: &'static str, _: usize) -> Self::SerializeTupleVariant => "tuple variant" + serialize_map(_: Option) -> Self::SerializeMap => "map" + serialize_struct(_: &'static str, _: usize) -> Self::SerializeStruct => "struct" + serialize_struct_variant(_: &'static str, _: u32, _: &'static str, _: usize) -> Self::SerializeStructVariant => "struct variant" + } + } +} diff --git a/src/serde_multipart/unserializers/input_file.rs b/src/serde_multipart/unserializers/input_file.rs new file mode 100644 index 00000000..d3cc94ff --- /dev/null +++ b/src/serde_multipart/unserializers/input_file.rs @@ -0,0 +1,173 @@ +use std::borrow::Cow; + +use serde::{ + ser::{Impossible, SerializeStructVariant}, + Serialize, Serializer, +}; + +use crate::{ + serde_multipart::unserializers::{ + bytes::BytesUnserializer, string::StringUnserializer, UnserializerError, + }, + types::InputFile, +}; + +pub(crate) enum InputFileUnserializer { + Memory { file_name: String, data: Cow<'static, [u8]> }, + NotMem, +} + +impl InputFileUnserializer { + pub(crate) fn memory() -> Self { + Self::Memory { file_name: String::new(), data: Cow::Borrowed(&[]) } + } +} + +impl Serializer for InputFileUnserializer { + type Ok = InputFile; + type Error = UnserializerError; + + type SerializeSeq = Impossible; + type SerializeTuple = Impossible; + type SerializeTupleStruct = Impossible; + type SerializeTupleVariant = Impossible; + type SerializeMap = Impossible; + type SerializeStruct = Impossible; + + type SerializeStructVariant = Self; + + fn serialize_newtype_variant( + self, + name: &'static str, + _: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: Serialize, + { + if name != "InputFile" { + return Err(UnserializerError::UnsupportedType { + ty: name, + supported: "InputFile", // TODO + }); + } + + // TODO + match variant { + "File" => Ok(InputFile::File(value.serialize(StringUnserializer)?.into())), + "Url" => Ok(InputFile::Url(value.serialize(StringUnserializer)?)), + "FileId" => Ok(InputFile::FileId(value.serialize(StringUnserializer)?)), + name => Err(UnserializerError::UnexpectedVariant { + name, + expected: &["File", "Url", "FileId"], // TODO + }), + } + } + + fn serialize_struct_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + if name != "InputFile" { + return Err(UnserializerError::UnsupportedType { ty: name, supported: "InputFile" }); + } + + if variant != "Memory" { + return Err(UnserializerError::UnexpectedVariant { + name: variant, + expected: &["Memory"], + }); + } + + if len != 2 { + return Err(UnserializerError::WrongLen { len, expected: 2 }); + } + + Ok(self) + } + + forward_to_unsuported_ty! { + supported: "Newtype variant, struct variant"; + simple { + serialize_bool bool + serialize_i8 i8 + serialize_i16 i16 + serialize_i32 i32 + serialize_i64 i64 + serialize_u8 u8 + serialize_u16 u16 + serialize_u32 u32 + serialize_u64 u64 + serialize_f32 f32 + serialize_f64 f64 + serialize_bytes &[u8] + serialize_char char + serialize_str &str + } + unit { + serialize_none "None" + serialize_unit "unit" + } + compound { + serialize_some(_: &T) -> Self::Ok => "Some(_)" + serialize_unit_struct(_: &'static str) -> Self::Ok => "unit struct" + serialize_unit_variant(_: &'static str, _: u32, _: &'static str) -> Self::Ok => "unit variant" + serialize_newtype_struct(_: &'static str, _: &T) -> Self::Ok => "newtype struct" + serialize_seq(_: Option) -> Self::SerializeSeq => "sequence" + serialize_tuple(_: usize) -> Self::SerializeTuple => "tuple" + serialize_tuple_struct(_: &'static str, _: usize) -> Self::SerializeTupleStruct => "tuple struct" + serialize_tuple_variant(_: &'static str, _: u32, _: &'static str, _: usize) -> Self::SerializeTupleVariant => "tuple variant" + serialize_map(_: Option) -> Self::SerializeMap => "map" + serialize_struct(_: &'static str, _: usize) -> Self::SerializeStruct => "struct" + } + } +} + +impl SerializeStructVariant for InputFileUnserializer { + type Ok = InputFile; + type Error = UnserializerError; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> + where + T: Serialize, + { + let (file_name, data) = match self { + Self::Memory { file_name, data } => (file_name, data), + Self::NotMem => { + *self = Self::memory(); + match self { + Self::Memory { file_name, data } => (file_name, data), + Self::NotMem => unreachable!(), + } + } + }; + + match key { + "file_name" => *file_name = value.serialize(StringUnserializer)?, + "data" => *data = Cow::Owned(value.serialize(BytesUnserializer::default())?), + name => { + return Err(UnserializerError::UnexpectedField { + name, + expected: &["file_name", "data"], // TODO + }); + } + } + + Ok(()) + } + + fn end(self) -> Result { + match self { + Self::Memory { file_name, data } => Ok(InputFile::Memory { file_name, data }), + Self::NotMem => unreachable!("struct without fields?"), + } + } +} diff --git a/src/serde_multipart/unserializers/string.rs b/src/serde_multipart/unserializers/string.rs new file mode 100644 index 00000000..7d4a8548 --- /dev/null +++ b/src/serde_multipart/unserializers/string.rs @@ -0,0 +1,61 @@ +use crate::serde_multipart::unserializers::UnserializerError; +use serde::{ser::Impossible, Serialize, Serializer}; + +pub(crate) struct StringUnserializer; + +impl Serializer for StringUnserializer { + type Ok = String; + type Error = UnserializerError; + + type SerializeSeq = Impossible; + type SerializeTuple = Impossible; + type SerializeTupleStruct = Impossible; + type SerializeTupleVariant = Impossible; + type SerializeMap = Impossible; + type SerializeStruct = Impossible; + type SerializeStructVariant = Impossible; + + fn serialize_char(self, v: char) -> Result { + Ok(v.to_string()) + } + + fn serialize_str(self, v: &str) -> Result { + Ok(v.to_owned()) + } + + forward_to_unsuported_ty! { + supported: "&str, String"; + simple { + serialize_bool bool + serialize_i8 i8 + serialize_i16 i16 + serialize_i32 i32 + serialize_i64 i64 + serialize_u8 u8 + serialize_u16 u16 + serialize_u32 u32 + serialize_u64 u64 + serialize_f32 f32 + serialize_f64 f64 + serialize_bytes &[u8] + } + unit { + serialize_none "None" + serialize_unit "unit" + } + compound { + serialize_some(_: &T) -> Self::Ok => "Some(_)" + serialize_unit_struct(_: &'static str) -> Self::Ok => "unit struct" + serialize_unit_variant(_: &'static str, _: u32, _: &'static str) -> Self::Ok => "unit variant" + serialize_newtype_struct(_: &'static str, _: &T) -> Self::Ok => "newtype struct" + serialize_newtype_variant(_: &'static str, _: u32, _: &'static str, _: &T) -> Self::Ok => "newtype variant" + serialize_seq(_: Option) -> Self::SerializeSeq => "sequence" + serialize_tuple(_: usize) -> Self::SerializeTuple => "tuple" + serialize_tuple_struct(_: &'static str, _: usize) -> Self::SerializeTupleStruct => "tuple struct" + serialize_tuple_variant(_: &'static str, _: u32, _: &'static str, _: usize) -> Self::SerializeTupleVariant => "tuple variant" + serialize_map(_: Option) -> Self::SerializeMap => "map" + serialize_struct(_: &'static str, _: usize) -> Self::SerializeStruct => "struct" + serialize_struct_variant(_: &'static str, _: u32, _: &'static str, _: usize) -> Self::SerializeStructVariant => "struct variant" + } + } +} diff --git a/src/types/input_file.rs b/src/types/input_file.rs index 55e15e14..d730493a 100644 --- a/src/types/input_file.rs +++ b/src/types/input_file.rs @@ -5,7 +5,7 @@ use std::{borrow::Cow, path::PathBuf}; /// This object represents the contents of a file to be uploaded. /// /// [The official docs](https://core.telegram.org/bots/api#inputfile). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize)] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[non_exhaustive] pub enum InputFile { File(PathBuf), @@ -75,27 +75,40 @@ impl From for Option { } } -impl Serialize for InputFile { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { +// internal api + +use reqwest::multipart::Part; + +impl InputFile { + pub(crate) async fn into_part(self) -> std::io::Result { + use bytes::{Bytes, BytesMut}; + use reqwest::Body; + use tokio_util::codec::{Decoder, FramedRead}; + + struct FileDecoder; + + impl Decoder for FileDecoder { + type Item = Bytes; + type Error = std::io::Error; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + if src.is_empty() { + return Ok(None); + } + Ok(Some(src.split().freeze())) + } + } + match self { - InputFile::File(path) => { - // NOTE: file should be actually attached with - // multipart/form-data - serializer.serialize_str( - // TODO: remove unwrap (?) - &format!("attach://{}", path.file_name().unwrap().to_string_lossy()), - ) + Self::File(path_to_file) => { + let file_name = path_to_file.file_name().unwrap().to_string_lossy().into_owned(); + + let file = FramedRead::new(tokio::fs::File::open(path_to_file).await?, FileDecoder); + + Ok(Part::stream(Body::wrap_stream(file)).file_name(file_name)) } - InputFile::Memory { data, .. } => { - // NOTE: file should be actually attached with - // multipart/form-data - serializer.serialize_str(&format!("attach://{}", String::from_utf8_lossy(data))) - } - InputFile::Url(url) => serializer.serialize_str(url), - InputFile::FileId(id) => serializer.serialize_str(id), + Self::Memory { file_name, data } => Ok(Part::bytes(data).file_name(file_name)), + Self::Url(s) | Self::FileId(s) => Ok(Part::text(s)), } } } From 5afe72b3683914d6e5b1ad6a5851475fde912b7d Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 16 Aug 2020 00:21:55 +0300 Subject: [PATCH 078/755] rewrite requests to use serde-multipart Rewrite multipart requests to use serde_multipart instead of FromBuilder --- src/net/mod.rs | 2 +- src/net/request.rs | 40 +++-- src/requests/all/add_sticker_to_set.rs | 33 ++-- src/requests/all/create_new_sticker_set.rs | 36 ++-- src/requests/all/edit_inline_message_media.rs | 20 +-- src/requests/all/edit_message_media.rs | 21 +-- src/requests/all/send_animation.rs | 35 +--- src/requests/all/send_audio.rs | 35 +--- src/requests/all/send_document.rs | 32 +--- src/requests/all/send_media_group.rs | 21 +-- src/requests/all/send_photo.rs | 29 +--- src/requests/all/send_sticker.rs | 27 +-- src/requests/all/send_video.rs | 36 +--- src/requests/all/send_video_note.rs | 32 +--- src/requests/all/send_voice.rs | 30 +--- src/requests/form_builder.rs | 156 ------------------ src/requests/mod.rs | 13 -- src/requests/utils.rs | 17 +- src/types/sticker_type.rs | 9 +- 19 files changed, 149 insertions(+), 475 deletions(-) delete mode 100644 src/requests/form_builder.rs diff --git a/src/net/mod.rs b/src/net/mod.rs index b4944959..3278fb28 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -17,7 +17,7 @@ const TELEGRAM_API_URL: &str = "https://api.telegram.org"; /// /// [Telegram documentation]: https://core.telegram.org/bots/api#making-requests fn method_url(base: &str, token: &str, method_name: &str) -> String { - format!("{url}/bot{token}/{method}", url = base, token = token, method = method_name,) + format!("{url}/bot{token}/{method}", url = base, token = token, method = method_name) } /// Creates URL for downloading a file. See the [Telegram documentation]. diff --git a/src/net/request.rs b/src/net/request.rs index e376c3fd..e84c3a47 100644 --- a/src/net/request.rs +++ b/src/net/request.rs @@ -1,25 +1,39 @@ -use reqwest::{multipart::Form, Client, Response}; +use std::time::Duration; + +use reqwest::{Client, Response}; use serde::{de::DeserializeOwned, Serialize}; -use crate::{requests::ResponseResult, RequestError}; - -use super::{TelegramResponse, TELEGRAM_API_URL}; -use std::time::Duration; +use crate::{ + net::{TelegramResponse, TELEGRAM_API_URL}, + requests::ResponseResult, + serde_multipart::to_form, + RequestError, +}; const DELAY_ON_SERVER_ERROR: Duration = Duration::from_secs(10); -pub async fn request_multipart( +pub async fn request_multipart( client: &Client, token: &str, method_name: &str, - params: Form, -) -> ResponseResult + params: &P, // I'll regret this +) -> ResponseResult where - T: DeserializeOwned, + P: Serialize, + R: DeserializeOwned, { + use crate::serde_multipart::Error; + let form = match to_form(params).await { + Ok(x) => x, + Err(Error::Io(ioerr)) => return Err(RequestError::Io(ioerr)), + Err(_) => unreachable!( + "we don't create requests those fail to serialize (if you see this, open an issue :|)" + ), + }; + let response = client .post(&super::method_url(TELEGRAM_API_URL, token, method_name)) - .multipart(params) + .multipart(form) .send() .await .map_err(RequestError::NetworkError)?; @@ -27,15 +41,15 @@ where process_response(response).await } -pub async fn request_json( +pub async fn request_json( client: &Client, token: &str, method_name: &str, params: &P, -) -> ResponseResult +) -> ResponseResult where - T: DeserializeOwned, P: Serialize, + R: DeserializeOwned, { let response = client .post(&super::method_url(TELEGRAM_API_URL, token, method_name)) diff --git a/src/requests/all/add_sticker_to_set.rs b/src/requests/all/add_sticker_to_set.rs index 8cc0188f..3408f36b 100644 --- a/src/requests/all/add_sticker_to_set.rs +++ b/src/requests/all/add_sticker_to_set.rs @@ -1,51 +1,38 @@ +use serde::Serialize; + use crate::{ net, - requests::form_builder::FormBuilder, types::{MaskPosition, True}, Bot, }; use crate::{ - requests::{RequestWithFile, ResponseResult}, + requests::{Request, ResponseResult}, types::StickerType, }; /// 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(Debug, Clone)] +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] pub struct AddStickerToSet { + #[serde(skip_serializing)] bot: Bot, user_id: i32, name: String, + #[serde(flatten)] sticker_type: StickerType, emojis: String, mask_position: Option, } #[async_trait::async_trait] -impl RequestWithFile for AddStickerToSet { +impl Request for AddStickerToSet { type Output = True; - async fn send(&self) -> tokio::io::Result> { - let builder = - FormBuilder::new().add_text("user_id", &self.user_id).add_text("name", &self.name); - - let builder = match &self.sticker_type { - StickerType::Png(file) => builder.add_input_file("png_sticker", &file), - StickerType::Tgs(file) => builder.add_input_file("tgs_sticker", &file), - } - .await? - .add_text("emojis", &self.emojis) - .add_text("mask_position", &self.mask_position); - - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "addStickerToSet", - builder.build(), - ) - .await) + async fn send(&self) -> ResponseResult { + net::request_multipart(self.bot.client(), self.bot.token(), "addStickerToSet", self).await } } diff --git a/src/requests/all/create_new_sticker_set.rs b/src/requests/all/create_new_sticker_set.rs index a49cb08b..d9c5e9da 100644 --- a/src/requests/all/create_new_sticker_set.rs +++ b/src/requests/all/create_new_sticker_set.rs @@ -1,6 +1,8 @@ +use serde::Serialize; + use crate::{ net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + requests::{Request, ResponseResult}, types::{MaskPosition, StickerType, True}, Bot, }; @@ -9,12 +11,15 @@ use crate::{ /// able to edit the created sticker set. /// /// [The official docs](https://core.telegram.org/bots/api#createnewstickerset). -#[derive(Debug, Clone)] +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] pub struct CreateNewStickerSet { + #[serde(skip_serializing)] bot: Bot, user_id: i32, name: String, title: String, + #[serde(flatten)] sticker_type: StickerType, emojis: String, contains_masks: Option, @@ -22,31 +27,12 @@ pub struct CreateNewStickerSet { } #[async_trait::async_trait] -impl RequestWithFile for CreateNewStickerSet { +impl Request for CreateNewStickerSet { type Output = True; - async fn send(&self) -> tokio::io::Result> { - let builder = FormBuilder::new() - .add_text("user_id", &self.user_id) - .add_text("name", &self.name) - .add_text("title", &self.title); - - let builder = match &self.sticker_type { - StickerType::Png(file) => builder.add_input_file("png_sticker", &file), - StickerType::Tgs(file) => builder.add_input_file("tgs_sticker", &file), - } - .await? - .add_text("emojis", &self.emojis) - .add_text("contains_masks", &self.contains_masks) - .add_text("mask_position", &self.mask_position); - - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "createNewStickerSet", - builder.build(), - ) - .await) + async fn send(&self) -> ResponseResult { + net::request_multipart(self.bot.client(), self.bot.token(), "createNewStickerSet", self) + .await } } diff --git a/src/requests/all/edit_inline_message_media.rs b/src/requests/all/edit_inline_message_media.rs index 9a11f126..ea0a4070 100644 --- a/src/requests/all/edit_inline_message_media.rs +++ b/src/requests/all/edit_inline_message_media.rs @@ -1,6 +1,8 @@ +use serde::Serialize; + use crate::{ net, - requests::{form_builder::FormBuilder, Request, ResponseResult}, + requests::{Request, ResponseResult}, types::{InlineKeyboardMarkup, InputMedia, True}, Bot, }; @@ -17,8 +19,10 @@ use crate::{ /// [The official docs](https://core.telegram.org/bots/api#editmessagemedia). /// /// [`True`]: crate::types::True -#[derive(Debug, Clone)] +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] pub struct EditInlineMessageMedia { + #[serde(skip_serializing)] bot: Bot, inline_message_id: String, media: InputMedia, @@ -30,17 +34,7 @@ impl Request for EditInlineMessageMedia { type Output = True; async fn send(&self) -> ResponseResult { - net::request_multipart( - self.bot.client(), - self.bot.token(), - "editMessageMedia", - FormBuilder::new() - .add_text("media", &self.media) - .add_text("reply_markup", &self.reply_markup) - .add_text("inline_message_id", &self.inline_message_id) - .build(), - ) - .await + net::request_multipart(self.bot.client(), self.bot.token(), "editMessageMedia", self).await } } diff --git a/src/requests/all/edit_message_media.rs b/src/requests/all/edit_message_media.rs index 7f15ed5f..826244db 100644 --- a/src/requests/all/edit_message_media.rs +++ b/src/requests/all/edit_message_media.rs @@ -1,6 +1,8 @@ +use serde::Serialize; + use crate::{ net, - requests::{form_builder::FormBuilder, Request, ResponseResult}, + requests::{Request, ResponseResult}, types::{ChatId, InlineKeyboardMarkup, InputMedia, Message}, Bot, }; @@ -15,8 +17,10 @@ use crate::{ /// [The official docs](https://core.telegram.org/bots/api#editmessagemedia). /// /// [`Message`]: crate::types::Message -#[derive(Debug, Clone)] +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] pub struct EditMessageMedia { + #[serde(skip_serializing)] bot: Bot, chat_id: ChatId, message_id: i32, @@ -29,18 +33,7 @@ impl Request for EditMessageMedia { type Output = Message; async fn send(&self) -> ResponseResult { - net::request_multipart( - self.bot.client(), - self.bot.token(), - "editMessageMedia", - FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_text("message_id", &self.message_id) - .add_text("media", &self.media) - .add_text("reply_markup", &self.reply_markup) - .build(), - ) - .await + net::request_multipart(self.bot.client(), self.bot.token(), "editMessageMedia", self).await } } diff --git a/src/requests/all/send_animation.rs b/src/requests/all/send_animation.rs index 287ebd93..6e5ef1cb 100644 --- a/src/requests/all/send_animation.rs +++ b/src/requests/all/send_animation.rs @@ -1,6 +1,8 @@ +use serde::Serialize; + use crate::{ net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + requests::{Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -12,8 +14,10 @@ use crate::{ /// may be changed in the future. /// /// [The official docs](https://core.telegram.org/bots/api#sendanimation). -#[derive(Debug, Clone)] +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] pub struct SendAnimation { + #[serde(skip_serializing)] bot: Bot, pub chat_id: ChatId, pub animation: InputFile, @@ -29,32 +33,11 @@ pub struct SendAnimation { } #[async_trait::async_trait] -impl RequestWithFile for SendAnimation { +impl Request for SendAnimation { type Output = Message; - async fn send(&self) -> tokio::io::Result> { - let mut builder = FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("animation", &self.animation) - .await? - .add_text("duration", &self.duration) - .add_text("width", &self.width) - .add_text("height", &self.height) - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup); - if let Some(thumb) = self.thumb.as_ref() { - builder = builder.add_input_file("thumb", thumb).await?; - } - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendAnimation", - builder.build(), - ) - .await) + async fn send(&self) -> ResponseResult { + net::request_multipart(self.bot.client(), self.bot.token(), "sendAnimation", self).await } } diff --git a/src/requests/all/send_audio.rs b/src/requests/all/send_audio.rs index f2ed209f..dc61458c 100644 --- a/src/requests/all/send_audio.rs +++ b/src/requests/all/send_audio.rs @@ -1,6 +1,8 @@ +use serde::Serialize; + use crate::{ net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + requests::{Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -16,8 +18,10 @@ use crate::{ /// [The official docs](https://core.telegram.org/bots/api#sendaudio). /// /// [`Bot::send_voice`]: crate::Bot::send_voice -#[derive(Debug, Clone)] +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] pub struct SendAudio { + #[serde(skip_serializing)] bot: Bot, chat_id: ChatId, audio: InputFile, @@ -33,32 +37,11 @@ pub struct SendAudio { } #[async_trait::async_trait] -impl RequestWithFile for SendAudio { +impl Request for SendAudio { type Output = Message; - async fn send(&self) -> tokio::io::Result> { - let mut builder = FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("audio", &self.audio) - .await? - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("duration", &self.duration) - .add_text("performer", &self.performer) - .add_text("title", &self.title) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup); - if let Some(thumb) = self.thumb.as_ref() { - builder = builder.add_input_file("thumb", thumb).await?; - } - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendAudio", - builder.build(), - ) - .await) + async fn send(&self) -> ResponseResult { + net::request_multipart(self.bot.client(), self.bot.token(), "sendAudio", self).await } } diff --git a/src/requests/all/send_document.rs b/src/requests/all/send_document.rs index a3b6ff6f..e865a008 100644 --- a/src/requests/all/send_document.rs +++ b/src/requests/all/send_document.rs @@ -1,6 +1,8 @@ +use serde::Serialize; + use crate::{ net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + requests::{Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -11,8 +13,10 @@ use crate::{ /// may be changed in the future. /// /// [The official docs](https://core.telegram.org/bots/api#senddocument). -#[derive(Debug, Clone)] +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] pub struct SendDocument { + #[serde(skip_serializing)] bot: Bot, chat_id: ChatId, document: InputFile, @@ -25,29 +29,11 @@ pub struct SendDocument { } #[async_trait::async_trait] -impl RequestWithFile for SendDocument { +impl Request for SendDocument { type Output = Message; - async fn send(&self) -> tokio::io::Result> { - let mut builder = FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("document", &self.document) - .await? - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup); - if let Some(thumb) = self.thumb.as_ref() { - builder = builder.add_input_file("thumb", thumb).await?; - } - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendDocument", - builder.build(), - ) - .await) + async fn send(&self) -> ResponseResult { + net::request_multipart(self.bot.client(), self.bot.token(), "sendDocument", self).await } } diff --git a/src/requests/all/send_media_group.rs b/src/requests/all/send_media_group.rs index 7484fc62..e7adb68a 100644 --- a/src/requests/all/send_media_group.rs +++ b/src/requests/all/send_media_group.rs @@ -1,6 +1,8 @@ +use serde::Serialize; + use crate::{ net, - requests::{form_builder::FormBuilder, Request, ResponseResult}, + requests::{Request, ResponseResult}, types::{ChatId, InputMedia, Message}, Bot, }; @@ -8,8 +10,10 @@ use crate::{ /// 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(Debug, Clone)] +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] pub struct SendMediaGroup { + #[serde(skip_serializing)] bot: Bot, chat_id: ChatId, media: Vec, // TODO: InputMediaPhoto and InputMediaVideo @@ -22,18 +26,7 @@ impl Request for SendMediaGroup { type Output = Vec; async fn send(&self) -> ResponseResult> { - net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendMediaGroup", - FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_text("media", &self.media) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .build(), - ) - .await + net::request_multipart(self.bot.client(), self.bot.token(), "sendMediaGroup", self).await } } diff --git a/src/requests/all/send_photo.rs b/src/requests/all/send_photo.rs index 4027f000..42ad8e6f 100644 --- a/src/requests/all/send_photo.rs +++ b/src/requests/all/send_photo.rs @@ -1,6 +1,8 @@ +use serde::Serialize; + use crate::{ net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + requests::{Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -8,8 +10,10 @@ use crate::{ /// Use this method to send photos. /// /// [The official docs](https://core.telegram.org/bots/api#sendphoto). -#[derive(Debug, Clone)] +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] pub struct SendPhoto { + #[serde(skip_serializing)] bot: Bot, chat_id: ChatId, photo: InputFile, @@ -21,26 +25,11 @@ pub struct SendPhoto { } #[async_trait::async_trait] -impl RequestWithFile for SendPhoto { +impl Request for SendPhoto { type Output = Message; - async fn send(&self) -> tokio::io::Result> { - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendPhoto", - FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("photo", &self.photo) - .await? - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup) - .build(), - ) - .await) + async fn send(&self) -> ResponseResult { + net::request_multipart(self.bot.client(), self.bot.token(), "sendPhoto", self).await } } diff --git a/src/requests/all/send_sticker.rs b/src/requests/all/send_sticker.rs index 5e019e19..dd25c9bb 100644 --- a/src/requests/all/send_sticker.rs +++ b/src/requests/all/send_sticker.rs @@ -1,6 +1,8 @@ +use serde::Serialize; + use crate::{ net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + requests::{Request, ResponseResult}, types::{ChatId, InputFile, Message, ReplyMarkup}, Bot, }; @@ -10,8 +12,10 @@ use crate::{ /// [The official docs](https://core.telegram.org/bots/api#sendsticker). /// /// [animated]: https://telegram.org/blog/animated-stickers -#[derive(Debug, Clone)] +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] pub struct SendSticker { + #[serde(skip_serializing)] bot: Bot, chat_id: ChatId, sticker: InputFile, @@ -21,24 +25,11 @@ pub struct SendSticker { } #[async_trait::async_trait] -impl RequestWithFile for SendSticker { +impl Request for SendSticker { type Output = Message; - async fn send(&self) -> tokio::io::Result> { - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendSticker", - FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("sticker", &self.sticker) - .await? - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup) - .build(), - ) - .await) + async fn send(&self) -> ResponseResult { + net::request_multipart(self.bot.client(), self.bot.token(), "sendSticker", self).await } } diff --git a/src/requests/all/send_video.rs b/src/requests/all/send_video.rs index 30ab6a45..3f218f10 100644 --- a/src/requests/all/send_video.rs +++ b/src/requests/all/send_video.rs @@ -1,6 +1,8 @@ +use serde::Serialize; + use crate::{ net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + requests::{Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -12,8 +14,10 @@ use crate::{ /// limit may be changed in the future. /// /// [The official docs](https://core.telegram.org/bots/api#sendvideo). -#[derive(Debug, Clone)] +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] pub struct SendVideo { + #[serde(skip_serializing)] bot: Bot, chat_id: ChatId, video: InputFile, @@ -30,33 +34,11 @@ pub struct SendVideo { } #[async_trait::async_trait] -impl RequestWithFile for SendVideo { +impl Request for SendVideo { type Output = Message; - async fn send(&self) -> tokio::io::Result> { - let mut builder = FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("video", &self.video) - .await? - .add_text("duration", &self.duration) - .add_text("width", &self.width) - .add_text("height", &self.height) - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("supports_streaming", &self.supports_streaming) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup); - if let Some(thumb) = self.thumb.as_ref() { - builder = builder.add_input_file("thumb", thumb).await?; - } - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendVideo", - builder.build(), - ) - .await) + async fn send(&self) -> ResponseResult { + net::request_multipart(self.bot.client(), self.bot.token(), "sendVideo", self).await } } diff --git a/src/requests/all/send_video_note.rs b/src/requests/all/send_video_note.rs index 2ac653be..9a0e07dd 100644 --- a/src/requests/all/send_video_note.rs +++ b/src/requests/all/send_video_note.rs @@ -1,6 +1,8 @@ +use serde::Serialize; + use crate::{ net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + requests::{Request, ResponseResult}, types::{ChatId, InputFile, Message, ReplyMarkup}, Bot, }; @@ -11,8 +13,10 @@ use crate::{ /// [The official docs](https://core.telegram.org/bots/api#sendvideonote). /// /// [v.4.0]: https://telegram.org/blog/video-messages-and-telescope -#[derive(Debug, Clone)] +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] pub struct SendVideoNote { + #[serde(skip_serializing)] bot: Bot, chat_id: ChatId, video_note: InputFile, @@ -25,29 +29,11 @@ pub struct SendVideoNote { } #[async_trait::async_trait] -impl RequestWithFile for SendVideoNote { +impl Request for SendVideoNote { type Output = Message; - async fn send(&self) -> tokio::io::Result> { - let mut builder = FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("video_note", &self.video_note) - .await? - .add_text("duration", &self.duration) - .add_text("length", &self.length) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup); - if let Some(thumb) = self.thumb.as_ref() { - builder = builder.add_input_file("thumb", thumb).await?; - } - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendVideoNote", - builder.build(), - ) - .await) + async fn send(&self) -> ResponseResult { + net::request_multipart(self.bot.client(), self.bot.token(), "sendVideoNote", self).await } } diff --git a/src/requests/all/send_voice.rs b/src/requests/all/send_voice.rs index 1e50bbed..6bfc301a 100644 --- a/src/requests/all/send_voice.rs +++ b/src/requests/all/send_voice.rs @@ -1,6 +1,8 @@ +use serde::Serialize; + use crate::{ net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, + requests::{Request, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -17,8 +19,10 @@ use crate::{ /// /// [`Audio`]: crate::types::Audio /// [`Document`]: crate::types::Document -#[derive(Debug, Clone)] +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, Clone, Serialize)] pub struct SendVoice { + #[serde(skip_serializing)] bot: Bot, chat_id: ChatId, voice: InputFile, @@ -31,27 +35,11 @@ pub struct SendVoice { } #[async_trait::async_trait] -impl RequestWithFile for SendVoice { +impl Request for SendVoice { type Output = Message; - async fn send(&self) -> tokio::io::Result> { - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendVoice", - FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("voice", &self.voice) - .await? - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("duration", &self.duration) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup) - .build(), - ) - .await) + async fn send(&self) -> ResponseResult { + net::request_multipart(self.bot.client(), self.bot.token(), "sendVoice", self).await } } diff --git a/src/requests/form_builder.rs b/src/requests/form_builder.rs deleted file mode 100644 index 3c7f70b9..00000000 --- a/src/requests/form_builder.rs +++ /dev/null @@ -1,156 +0,0 @@ -use std::{borrow::Cow, path::PathBuf}; - -use reqwest::multipart::Form; - -use crate::{ - requests::utils::{file_from_memory_to_part, file_to_part}, - types::{ - ChatId, InlineKeyboardMarkup, InputFile, InputMedia, MaskPosition, ParseMode, ReplyMarkup, - }, -}; - -/// This is a convenient struct that builds `reqwest::multipart::Form` -/// from scratch. -pub(crate) struct FormBuilder { - form: Form, -} - -impl FormBuilder { - pub(crate) fn new() -> Self { - Self { form: Form::new() } - } - - pub fn add_text<'a, T, N>(self, name: N, value: &T) -> Self - where - N: Into>, - T: IntoFormText, - { - match value.into_form_text() { - Some(val) => Self { form: self.form.text(name.into().into_owned(), val) }, - None => self, - } - } - - pub async fn add_input_file<'a, N>(self, name: N, value: &InputFile) -> tokio::io::Result - where - N: Into>, - { - Ok(match value { - InputFile::File(path) => self.add_file(name, path.clone()).await?, - InputFile::Memory { file_name, data } => { - self.add_file_from_memory(name, file_name.clone(), data.clone()) - } - InputFile::Url(url) => self.add_text(name, url), - InputFile::FileId(file_id) => self.add_text(name, file_id), - }) - } - - // used in SendMediaGroup - pub async fn add_file<'a, N>(self, name: N, path_to_file: PathBuf) -> tokio::io::Result - where - N: Into>, - { - Ok(Self { - form: self.form.part(name.into().into_owned(), file_to_part(path_to_file).await?), - }) - } - - fn add_file_from_memory<'a, N>( - self, - name: N, - file_name: String, - data: Cow<'static, [u8]>, - ) -> Self - where - N: Into>, - { - Self { - form: self - .form - .part(name.into().into_owned(), file_from_memory_to_part(data, file_name)), - } - } - - pub fn build(self) -> Form { - self.form - } -} - -pub(crate) trait IntoFormText { - fn into_form_text(&self) -> Option; -} - -macro_rules! impl_for_struct { - ($($name:ty),*) => { - $( - impl IntoFormText for $name { - fn into_form_text(&self) -> Option { - let json = serde_json::to_string(self) - .expect("serde_json::to_string failed"); - Some(json) - } - } - )* - }; -} - -impl_for_struct!(bool, i32, i64, u32, ReplyMarkup, InlineKeyboardMarkup, MaskPosition); - -impl IntoFormText for Option -where - T: IntoFormText, -{ - fn into_form_text(&self) -> Option { - self.as_ref().and_then(IntoFormText::into_form_text) - } -} - -// TODO: fix InputMedia implementation of IntoFormValue (for now it doesn't -// encode files :|) -impl IntoFormText for Vec { - fn into_form_text(&self) -> Option { - let json = serde_json::to_string(self).expect("serde_json::to_string failed"); - Some(json) - } -} - -impl IntoFormText for InputMedia { - fn into_form_text(&self) -> Option { - let json = serde_json::to_string(self).expect("serde_json::to_string failed"); - Some(json) - } -} - -impl IntoFormText for str { - fn into_form_text(&self) -> Option { - Some(self.to_owned()) - } -} - -impl IntoFormText for ParseMode { - fn into_form_text(&self) -> Option { - let string = match self { - ParseMode::MarkdownV2 => String::from("MarkdownV2"), - ParseMode::HTML => String::from("HTML"), - #[allow(deprecated)] - ParseMode::Markdown => String::from("Markdown"), - }; - Some(string) - } -} - -impl IntoFormText for ChatId { - fn into_form_text(&self) -> Option { - let string = match self { - ChatId::Id(id) => id.to_string(), - ChatId::ChannelUsername(username) => username.clone(), - }; - Some(string) - } -} - -impl IntoFormText for String { - fn into_form_text(&self) -> Option { - Some(self.clone()) - } -} diff --git a/src/requests/mod.rs b/src/requests/mod.rs index 09f2af6c..86536cb9 100644 --- a/src/requests/mod.rs +++ b/src/requests/mod.rs @@ -1,7 +1,6 @@ //! API requests. mod all; -mod form_builder; mod utils; pub use all::*; @@ -18,15 +17,3 @@ pub trait Request { /// Asynchronously sends this request to Telegram and returns the result. async fn send(&self) -> ResponseResult; } - -/// Designates an API request with possibly sending file. -#[async_trait::async_trait] -pub trait RequestWithFile { - /// A data structure returned if success. - type Output; - - /// Asynchronously sends this request to Telegram and returns the result. - /// Returns `tokio::io::Result::Err` when trying to send file which does not - /// exists. - async fn send(&self) -> tokio::io::Result>; -} diff --git a/src/requests/utils.rs b/src/requests/utils.rs index f10c9dc5..36e7510e 100644 --- a/src/requests/utils.rs +++ b/src/requests/utils.rs @@ -1,8 +1,5 @@ -use std::{borrow::Cow, path::PathBuf}; - use bytes::{Bytes, BytesMut}; -use reqwest::{multipart::Part, Body}; -use tokio_util::codec::{Decoder, FramedRead}; +use tokio_util::codec::Decoder; struct FileDecoder; @@ -17,15 +14,3 @@ impl Decoder for FileDecoder { Ok(Some(src.split().freeze())) } } - -pub async fn file_to_part(path_to_file: PathBuf) -> std::io::Result { - let file_name = path_to_file.file_name().unwrap().to_string_lossy().into_owned(); - - let file = FramedRead::new(tokio::fs::File::open(path_to_file).await?, FileDecoder); - - Ok(Part::stream(Body::wrap_stream(file)).file_name(file_name)) -} - -pub fn file_from_memory_to_part(data: Cow<'static, [u8]>, name: String) -> Part { - Part::bytes(data).file_name(name) -} diff --git a/src/types/sticker_type.rs b/src/types/sticker_type.rs index b80added..ffb7618e 100644 --- a/src/types/sticker_type.rs +++ b/src/types/sticker_type.rs @@ -1,7 +1,10 @@ +use serde::Serialize; + use crate::types::InputFile; -#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)] #[non_exhaustive] +#[serde(untagged)] pub enum StickerType { /// PNG image with the sticker, must be up to 512 kilobytes in size, /// dimensions must not exceed 512px, and either width or height must be @@ -17,10 +20,10 @@ pub enum StickerType { /// [`InputFile::FileId`]: crate::types::InputFile::FileId /// /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files - Png(InputFile), + Png { png_sticker: InputFile }, /// TGS animation with the sticker, uploaded using multipart/form-data. /// /// See https://core.telegram.org/animated_stickers#technical-requirements for technical requirements - Tgs(InputFile), + Tgs { tgs_sticker: InputFile }, } From 72b776d859bf6ee78038cbb7a198027a2ecea662 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 16 Aug 2020 10:53:15 +0300 Subject: [PATCH 079/755] some cleanup (serde_multipart) --- Cargo.toml | 2 +- src/serde_multipart/serializers.rs | 6 +++--- src/types/input_media.rs | 10 +++++----- src/types/sticker_type.rs | 20 ++++++++++++++++++++ 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 22724491..72ea17a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ authors = [ [dependencies] futures = "0.3.5" -tokio = { version = "0.2.21", features = ["fs", "stream", "rt-threaded", "macros"] } +tokio = { version = "0.2.21", features = ["fs", "stream"] } tokio-util = "0.3.1" bytes = "0.5.5" async-trait = "0.1.36" diff --git a/src/serde_multipart/serializers.rs b/src/serde_multipart/serializers.rs index 7e45e4f9..3e9e7168 100644 --- a/src/serde_multipart/serializers.rs +++ b/src/serde_multipart/serializers.rs @@ -19,7 +19,7 @@ use std::{fmt, fmt::Display, io}; pub(crate) enum Error { Custom(String), TopLevelNotStruct, - InputFileUnserializerError(crate::serde_multipart::unserializers::UnserializerError), + InputFileUnserializer(crate::serde_multipart::unserializers::UnserializerError), Io(std::io::Error), Json(serde_json::Error), } @@ -38,7 +38,7 @@ impl Display for Error { match self { Self::Custom(s) => write!(f, "Custom serde error: {}", s), Self::TopLevelNotStruct => write!(f, "Multipart supports only structs at top level"), - Self::InputFileUnserializerError(inner) => { + Self::InputFileUnserializer(inner) => { write!(f, "Error while unserializing input file: {}", inner) } Self::Io(inner) => write!(f, "Io error: {}", inner), @@ -541,7 +541,7 @@ impl SerializeStructVariant for PartFromFile { where T: Serialize, { - self.inner.serialize_field(key, value).map_err(Error::InputFileUnserializerError) + self.inner.serialize_field(key, value).map_err(Error::InputFileUnserializer) } fn end(self) -> Result { diff --git a/src/types/input_media.rs b/src/types/input_media.rs index a37ebda4..160f9af8 100644 --- a/src/types/input_media.rs +++ b/src/types/input_media.rs @@ -432,7 +432,7 @@ mod tests { #[test] fn photo_serialize() { - let expected_json = r#"{"type":"photo","media":"123456"}"#; + let expected_json = r#"{"type":"photo","media":{"FileId":"123456"}}"#; let photo = InputMedia::Photo(InputMediaPhoto { media: InputFile::FileId(String::from("123456")), caption: None, @@ -445,7 +445,7 @@ mod tests { #[test] fn video_serialize() { - let expected_json = r#"{"type":"video","media":"123456"}"#; + let expected_json = r#"{"type":"video","media":{"FileId":"123456"}}"#; let video = InputMedia::Video(InputMediaVideo { media: InputFile::FileId(String::from("123456")), thumb: None, @@ -463,7 +463,7 @@ mod tests { #[test] fn animation_serialize() { - let expected_json = r#"{"type":"animation","media":"123456"}"#; + let expected_json = r#"{"type":"animation","media":{"FileId":"123456"}}"#; let video = InputMedia::Animation(InputMediaAnimation { media: InputFile::FileId(String::from("123456")), thumb: None, @@ -480,7 +480,7 @@ mod tests { #[test] fn audio_serialize() { - let expected_json = r#"{"type":"audio","media":"123456"}"#; + let expected_json = r#"{"type":"audio","media":{"FileId":"123456"}}"#; let video = InputMedia::Audio(InputMediaAudio { media: InputFile::FileId(String::from("123456")), thumb: None, @@ -497,7 +497,7 @@ mod tests { #[test] fn document_serialize() { - let expected_json = r#"{"type":"document","media":"123456"}"#; + let expected_json = r#"{"type":"document","media":{"FileId":"123456"}}"#; let video = InputMedia::Document(InputMediaDocument { media: InputFile::FileId(String::from("123456")), thumb: None, diff --git a/src/types/sticker_type.rs b/src/types/sticker_type.rs index ffb7618e..1ade552b 100644 --- a/src/types/sticker_type.rs +++ b/src/types/sticker_type.rs @@ -27,3 +27,23 @@ pub enum StickerType { /// See https://core.telegram.org/animated_stickers#technical-requirements for technical requirements Tgs { tgs_sticker: InputFile }, } + +impl StickerType { + /// Create png-`StickerType`. + /// + /// See [`StickerType::Png`] for more + /// + /// [`StickerType::Png`]: crate::types::StickerType::Png + fn png(png_sticker: InputFile) -> Self { + Self::Png { png_sticker } + } + + /// Create tgs-`StickerType`. + /// + /// See [`StickerType::Tgs`] for more + /// + /// [`StickerType::Tgs`]: crate::types::StickerType::Tgs + fn tgs(tgs_sticker: InputFile) -> Self { + Self::Tgs{ tgs_sticker } + } +} From f7aef22fbf08d335efb1d69297933a89488d9e3a Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 16 Aug 2020 10:58:10 +0300 Subject: [PATCH 080/755] make `StickerType::{png,tgs}` pub --- src/types/sticker_type.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/sticker_type.rs b/src/types/sticker_type.rs index 1ade552b..964385cd 100644 --- a/src/types/sticker_type.rs +++ b/src/types/sticker_type.rs @@ -34,7 +34,7 @@ impl StickerType { /// See [`StickerType::Png`] for more /// /// [`StickerType::Png`]: crate::types::StickerType::Png - fn png(png_sticker: InputFile) -> Self { + pub fn png(png_sticker: InputFile) -> Self { Self::Png { png_sticker } } @@ -43,7 +43,7 @@ impl StickerType { /// See [`StickerType::Tgs`] for more /// /// [`StickerType::Tgs`]: crate::types::StickerType::Tgs - fn tgs(tgs_sticker: InputFile) -> Self { + pub fn tgs(tgs_sticker: InputFile) -> Self { Self::Tgs{ tgs_sticker } } } From f0709a16b3611167184dd3435f7e1033dbeaaf01 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 16 Aug 2020 11:06:50 +0300 Subject: [PATCH 081/755] add changes to `CHANGELOG.md` (& a bit of fmt :facepalm:) --- CHANGELOG.md | 11 +++++++++++ src/types/sticker_type.rs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 738c0045..0ebc11fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `client_from_env` was moved from `teloxide::utils` to crate root of `teloxide-core` - To simplify `GetUpdates` request it was changed to simply return `Vec` (instead of `Vec>`) + +### Changed + +- Changed internal mechanism of sending multipart requests +- Added `RequestError::Io(io::Error)` to wrap I/O error those can happen while sending files to telegram +- Change `StickerType`: instead of newtypes (`Png(InputFile)`) use structs (`Png { png_sticker: InputFile }`), add + `StickerType::{png,tgs}` constructors + +### Removed + +- `RequestWithFile`, now multipart requests use `Request` [`teloxide`]: https://github.com/teloxide/teloxide diff --git a/src/types/sticker_type.rs b/src/types/sticker_type.rs index 964385cd..607b32c6 100644 --- a/src/types/sticker_type.rs +++ b/src/types/sticker_type.rs @@ -44,6 +44,6 @@ impl StickerType { /// /// [`StickerType::Tgs`]: crate::types::StickerType::Tgs pub fn tgs(tgs_sticker: InputFile) -> Self { - Self::Tgs{ tgs_sticker } + Self::Tgs { tgs_sticker } } } From 891106cabe923862100e4e18afe7456aa583a9c0 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Sun, 16 Aug 2020 14:44:08 +0300 Subject: [PATCH 082/755] Update src/errors.rs Co-authored-by: Temirkhan Myrzamadi --- src/errors.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/errors.rs b/src/errors.rs index 83fc2d42..999692be 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -37,6 +37,7 @@ pub enum RequestError { #[error("An error while parsing JSON: {0}")] InvalidJson(#[source] serde_json::Error), + // Occurs when trying to send a file to Telegram. #[error("An I/O error: {0}")] Io(#[source] io::Error), } From d186a73eda6729f856c74c2d1111413e3153b3e3 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 16 Aug 2020 20:02:12 +0300 Subject: [PATCH 083/755] add `GetUpdatesNonStrict` 'telegram' method - fail proof version of `GetUpdates` --- CHANGELOG.md | 2 + src/bot/api.rs | 30 ++++-- src/requests/all/get_updates.rs | 4 +- src/requests/all/get_updates_non_strict.rs | 97 +++++++++++++++++++ src/requests/all/mod.rs | 2 + src/types/chat.rs | 2 +- src/types/non_telegram_types/mod.rs | 2 + .../non_telegram_types/non_strict_vec.rs | 49 ++++++++++ 8 files changed, 175 insertions(+), 13 deletions(-) create mode 100644 src/requests/all/get_updates_non_strict.rs create mode 100644 src/types/non_telegram_types/non_strict_vec.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ebc11fe..bf1b58fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `client_from_env` was moved from `teloxide::utils` to crate root of `teloxide-core` - To simplify `GetUpdates` request it was changed to simply return `Vec` (instead of `Vec>`) +- `GetUpdatesNonStrict` 'telegram' method, that behaves just like `GetUpdates` but doesn't + fail if one of updates fails to be deserialized ### Changed diff --git a/src/bot/api.rs b/src/bot/api.rs index 8fe0fc80..bd7e08de 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -7,15 +7,15 @@ use crate::{ EditInlineMessageText, EditMessageCaption, EditMessageLiveLocation, EditMessageMedia, EditMessageReplyMarkup, EditMessageText, ExportChatInviteLink, ForwardMessage, GetChat, GetChatAdministrators, GetChatMember, GetChatMembersCount, GetFile, GetGameHighScores, - GetMe, GetMyCommands, GetStickerSet, GetUpdates, GetUserProfilePhotos, GetWebhookInfo, - KickChatMember, LeaveChat, PinChatMessage, PromoteChatMember, RestrictChatMember, - SendAnimation, SendAudio, SendChatAction, SendChatActionKind, SendContact, SendDice, - SendDocument, SendGame, SendInvoice, SendLocation, SendMediaGroup, SendMessage, SendPhoto, - SendPoll, SendSticker, SendVenue, SendVideo, SendVideoNote, SendVoice, - SetChatAdministratorCustomTitle, SetChatDescription, SetChatPermissions, SetChatPhoto, - SetChatStickerSet, SetChatTitle, SetGameScore, SetMyCommands, SetStickerPositionInSet, - SetStickerSetThumb, SetWebhook, StopInlineMessageLiveLocation, StopMessageLiveLocation, - StopPoll, UnbanChatMember, UnpinChatMessage, UploadStickerFile, + GetMe, GetMyCommands, GetStickerSet, GetUpdates, GetUpdatesNonStrict, GetUserProfilePhotos, + GetWebhookInfo, KickChatMember, LeaveChat, PinChatMessage, PromoteChatMember, + RestrictChatMember, SendAnimation, SendAudio, SendChatAction, SendChatActionKind, + SendContact, SendDice, SendDocument, SendGame, SendInvoice, SendLocation, SendMediaGroup, + SendMessage, SendPhoto, SendPoll, SendSticker, SendVenue, SendVideo, SendVideoNote, + SendVoice, SetChatAdministratorCustomTitle, SetChatDescription, SetChatPermissions, + SetChatPhoto, SetChatStickerSet, SetChatTitle, SetGameScore, SetMyCommands, + SetStickerPositionInSet, SetStickerSetThumb, SetWebhook, StopInlineMessageLiveLocation, + StopMessageLiveLocation, StopPoll, UnbanChatMember, UnpinChatMessage, UploadStickerFile, }, types::{ BotCommand, ChatId, ChatPermissions, InlineQueryResult, InputFile, InputMedia, @@ -39,6 +39,18 @@ impl Bot { GetUpdates::new(self.clone()) } + /// This is non strict version of [`get_updates`], this means that if it + /// will fail to deserialize some updates, it won't fail entirely, but + /// will just return some errors. + /// + /// Note: this is not a 'real' telegram method, this is simply + /// [`get_updates`] with changed return type. + /// + /// [`get_updates`]: crate::Bot::get_updates + pub fn get_updates_non_strict(&self) -> GetUpdatesNonStrict { + GetUpdatesNonStrict::new(self.clone()) + } + /// Use this method to specify a url and receive incoming updates via an /// outgoing webhook. /// diff --git a/src/requests/all/get_updates.rs b/src/requests/all/get_updates.rs index f6fe2d2b..a9f4558d 100644 --- a/src/requests/all/get_updates.rs +++ b/src/requests/all/get_updates.rs @@ -21,15 +21,13 @@ use crate::{ #[derive(Debug, Clone, Serialize)] pub struct GetUpdates { #[serde(skip_serializing)] - bot: Bot, + pub(crate) bot: Bot, pub(crate) offset: Option, pub(crate) limit: Option, pub(crate) timeout: Option, pub(crate) allowed_updates: Option>, } -// TODO: Add version of this method that will ignore unparsed updates, -// to be bullet proof #[async_trait::async_trait] impl Request for GetUpdates { type Output = Vec; diff --git a/src/requests/all/get_updates_non_strict.rs b/src/requests/all/get_updates_non_strict.rs new file mode 100644 index 00000000..67d326c8 --- /dev/null +++ b/src/requests/all/get_updates_non_strict.rs @@ -0,0 +1,97 @@ +use serde::Serialize; + +use crate::{ + net, + requests::{GetUpdates, Request, ResponseResult}, + types::{AllowedUpdate, NonStrictVec, Update}, + Bot, +}; + +/// This is non strict version of [`GetUpdates`], this means that if it will +/// fail to deserialize some updates, it won't fail entirely, but will just +/// return some errors. +/// +/// Note: this is not a 'real' telegram method, this is simply [`GetUpdates`] +/// with changed return type. +/// +/// [`GetUpdates`]: crate::requests::GetUpdates +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct GetUpdatesNonStrict(pub GetUpdates); + +#[async_trait::async_trait] +impl Request for GetUpdatesNonStrict { + type Output = NonStrictVec; + + async fn send(&self) -> ResponseResult { + net::request_json(self.0.bot.client(), self.0.bot.token(), "getUpdates", &self).await + } +} + +impl GetUpdatesNonStrict { + pub(crate) fn new(bot: Bot) -> Self { + Self(GetUpdates::new(bot)) + } + + /// Identifier of the first update to be returned. + /// + /// Must be greater by one than the highest among the identifiers of + /// previously received updates. By default, updates starting with the + /// earliest unconfirmed update are returned. An update is considered + /// confirmed as soon as [`GetUpdates`] is called with an [`offset`] + /// higher than its [`id`]. The negative offset can be specified to + /// retrieve updates starting from `-offset` update from the end of the + /// updates queue. All previous updates will forgotten. + /// + /// [`GetUpdates`]: self::GetUpdates + /// [`offset`]: self::GetUpdates::offset + /// [`id`]: crate::types::Update::id + pub fn offset(mut self, value: i32) -> Self { + self.0.offset = Some(value); + self + } + + /// Limits the number of updates to be retrieved. + /// + /// Values between `1`—`100` are accepted. Defaults to `100`. + pub fn limit(mut self, value: u8) -> Self { + self.0.limit = Some(value); + self + } + + /// Timeout in seconds for long polling. + /// + /// Defaults to `0`, i.e. usual short polling. Should be positive, short + /// polling should be used for testing purposes only. + pub fn timeout(mut self, value: u32) -> Self { + self.0.timeout = Some(value); + self + } + + /// List the types of updates you want your bot to receive. + /// + /// For example, specify [[`Message`], [`EditedChannelPost`], + /// [`CallbackQuery`]] to only receive updates of these types. + /// See [`AllowedUpdate`] for a complete list of available update types. + /// + /// Specify an empty list to receive all updates regardless of type + /// (default). If not specified, the previous setting will be used. + /// + /// **Note:** + /// This parameter doesn't affect updates created before the call to the + /// [`Bot::get_updates`], so unwanted updates may be received for a short + /// period of time. + /// + /// [`Message`]: self::AllowedUpdate::Message + /// [`EditedChannelPost`]: self::AllowedUpdate::EditedChannelPost + /// [`CallbackQuery`]: self::AllowedUpdate::CallbackQuery + /// [`AllowedUpdate`]: self::AllowedUpdate + /// [`Bot::get_updates`]: crate::Bot::get_updates + pub fn allowed_updates(mut self, value: T) -> Self + where + T: Into>, + { + self.0.allowed_updates = Some(value.into()); + self + } +} diff --git a/src/requests/all/mod.rs b/src/requests/all/mod.rs index 3de4d87a..d173f210 100644 --- a/src/requests/all/mod.rs +++ b/src/requests/all/mod.rs @@ -31,6 +31,7 @@ mod get_me; mod get_my_commands; mod get_sticker_set; mod get_updates; +mod get_updates_non_strict; mod get_user_profile_photos; mod get_webhook_info; mod kick_chat_member; @@ -107,6 +108,7 @@ pub use get_me::*; pub use get_my_commands::*; pub use get_sticker_set::*; pub use get_updates::*; +pub use get_updates_non_strict::*; pub use get_user_profile_photos::*; pub use get_webhook_info::*; pub use kick_chat_member::*; diff --git a/src/types/chat.rs b/src/types/chat.rs index 04c1bd49..c278d415 100644 --- a/src/types/chat.rs +++ b/src/types/chat.rs @@ -266,7 +266,7 @@ impl<'de> serde::de::Visitor<'de> for PrivateChatKindVisitor { write!(f, r#"field equal to "private""#) } - fn visit_borrowed_str(self, v: &'de str) -> Result { + fn visit_str(self, v: &str) -> Result { match v { "private" => Ok(()), _ => Err(E::invalid_value(serde::de::Unexpected::Str(v), &r#""private""#)), diff --git a/src/types/non_telegram_types/mod.rs b/src/types/non_telegram_types/mod.rs index 8add54b6..6c8e1636 100644 --- a/src/types/non_telegram_types/mod.rs +++ b/src/types/non_telegram_types/mod.rs @@ -1,7 +1,9 @@ pub use country_code::*; pub use currency::*; pub use mime_wrapper::*; +pub use non_strict_vec::*; mod country_code; mod currency; mod mime_wrapper; +mod non_strict_vec; diff --git a/src/types/non_telegram_types/non_strict_vec.rs b/src/types/non_telegram_types/non_strict_vec.rs new file mode 100644 index 00000000..0ae479c6 --- /dev/null +++ b/src/types/non_telegram_types/non_strict_vec.rs @@ -0,0 +1,49 @@ +use serde::de::DeserializeOwned; +use serde_json::{from_value, Value}; + +/// Similar to `Vec` but if it fails to deserialize element, it just saves +/// `Err((value, err))` +#[derive(Debug, serde::Deserialize)] +#[serde(from = "Vec")] +#[serde(bound = "T: DeserializeOwned")] +pub struct NonStrictVec(pub Vec>); + +impl From> for NonStrictVec { + fn from(vec: Vec) -> Self { + Self(vec.into_iter().map(|val| from_value(val.clone()).map_err(|e| (val, e))).collect()) + } +} + +#[test] +fn test() { + use crate::types::Update; + + let x: NonStrictVec = serde_json::from_str( + r#"[{ + "update_id": 923808447, + "message": { + "message_id": 361678, + "from": { + "id": 218485655, + "is_bot": false, + "first_name": "ваŅ„ĐĩĐģŅŒ", + "last_name": "🧇", + "username": "WaffleLapkin", + "language_code": "en" + }, + "chat": { + "id": 218485655, + "first_name": "ваŅ„ĐĩĐģŅŒ", + "last_name": "🧇", + "username": "WaffleLapkin", + "type": "private" + }, + "date": 1595860067, + "text": "s" + } + }]"#, + ) + .unwrap(); + + assert!(x.0.first().unwrap().is_ok()) +} From d4c0bc2fe15fec14acf045ed3f94c60c09c8dda7 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 16 Aug 2020 21:10:42 +0300 Subject: [PATCH 084/755] make all methods fields public --- src/requests/all/add_sticker_to_set.rs | 10 ++-- src/requests/all/answer_callback_query.rs | 10 ++-- src/requests/all/answer_inline_query.rs | 14 +++--- src/requests/all/answer_pre_checkout_query.rs | 6 +-- src/requests/all/answer_shipping_query.rs | 8 ++-- src/requests/all/create_new_sticker_set.rs | 14 +++--- src/requests/all/delete_chat_photo.rs | 2 +- src/requests/all/delete_chat_sticker_set.rs | 2 +- src/requests/all/delete_message.rs | 4 +- src/requests/all/delete_sticker_from_set.rs | 2 +- .../all/edit_inline_message_caption.rs | 8 ++-- .../all/edit_inline_message_live_location.rs | 8 ++-- src/requests/all/edit_inline_message_media.rs | 6 +-- .../all/edit_inline_message_reply_markup.rs | 4 +- src/requests/all/edit_inline_message_text.rs | 10 ++-- src/requests/all/edit_message_caption.rs | 10 ++-- .../all/edit_message_live_location.rs | 10 ++-- src/requests/all/edit_message_media.rs | 8 ++-- src/requests/all/edit_message_reply_markup.rs | 6 +-- src/requests/all/edit_message_text.rs | 12 ++--- src/requests/all/export_chat_invite_link.rs | 2 +- src/requests/all/forward_message.rs | 8 ++-- src/requests/all/get_chat.rs | 2 +- src/requests/all/get_chat_administrators.rs | 2 +- src/requests/all/get_chat_member.rs | 4 +- src/requests/all/get_chat_members_count.rs | 2 +- src/requests/all/get_file.rs | 2 +- src/requests/all/get_game_high_scores.rs | 4 +- src/requests/all/get_sticker_set.rs | 2 +- src/requests/all/get_updates.rs | 8 ++-- src/requests/all/get_user_profile_photos.rs | 6 +-- src/requests/all/kick_chat_member.rs | 6 +-- src/requests/all/leave_chat.rs | 2 +- src/requests/all/pin_chat_message.rs | 6 +-- src/requests/all/promote_chat_member.rs | 20 ++++---- src/requests/all/restrict_chat_member.rs | 8 ++-- src/requests/all/send_audio.rs | 22 ++++----- src/requests/all/send_chat_action.rs | 4 +- src/requests/all/send_contact.rs | 16 +++---- src/requests/all/send_dice.rs | 11 ++--- src/requests/all/send_document.rs | 16 +++---- src/requests/all/send_game.rs | 10 ++-- src/requests/all/send_invoice.rs | 46 +++++++++---------- src/requests/all/send_location.rs | 14 +++--- src/requests/all/send_media_group.rs | 8 ++-- src/requests/all/send_photo.rs | 14 +++--- src/requests/all/send_poll.rs | 30 ++++++------ src/requests/all/send_sticker.rs | 10 ++-- src/requests/all/send_venue.rs | 20 ++++---- src/requests/all/send_video.rs | 24 +++++----- src/requests/all/send_video_note.rs | 16 +++---- src/requests/all/send_voice.rs | 16 +++---- .../set_chat_administrator_custom_title.rs | 6 +-- src/requests/all/set_chat_description.rs | 4 +- src/requests/all/set_chat_permissions.rs | 4 +- src/requests/all/set_chat_photo.rs | 4 +- src/requests/all/set_chat_sticker_set.rs | 4 +- src/requests/all/set_chat_title.rs | 4 +- src/requests/all/set_game_score.rs | 10 ++-- src/requests/all/set_my_commands.rs | 3 +- .../all/set_sticker_position_in_set.rs | 4 +- src/requests/all/set_sticker_set_thumb.rs | 6 +-- src/requests/all/set_webhook.rs | 8 ++-- .../all/stop_inline_message_live_location.rs | 4 +- .../all/stop_message_live_location.rs | 6 +-- src/requests/all/stop_poll.rs | 6 +-- src/requests/all/unban_chat_member.rs | 4 +- src/requests/all/unpin_chat_message.rs | 2 +- src/requests/all/upload_sticker_file.rs | 4 +- 69 files changed, 298 insertions(+), 300 deletions(-) diff --git a/src/requests/all/add_sticker_to_set.rs b/src/requests/all/add_sticker_to_set.rs index 3408f36b..d78571bc 100644 --- a/src/requests/all/add_sticker_to_set.rs +++ b/src/requests/all/add_sticker_to_set.rs @@ -19,12 +19,12 @@ use crate::{ pub struct AddStickerToSet { #[serde(skip_serializing)] bot: Bot, - user_id: i32, - name: String, + pub user_id: i32, + pub name: String, #[serde(flatten)] - sticker_type: StickerType, - emojis: String, - mask_position: Option, + pub sticker_type: StickerType, + pub emojis: String, + pub mask_position: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/answer_callback_query.rs b/src/requests/all/answer_callback_query.rs index c74b1e88..254f2f07 100644 --- a/src/requests/all/answer_callback_query.rs +++ b/src/requests/all/answer_callback_query.rs @@ -21,11 +21,11 @@ use crate::{ pub struct AnswerCallbackQuery { #[serde(skip_serializing)] bot: Bot, - callback_query_id: String, - text: Option, - show_alert: Option, - url: Option, - cache_time: Option, + pub callback_query_id: String, + pub text: Option, + pub show_alert: Option, + pub url: Option, + pub cache_time: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/answer_inline_query.rs b/src/requests/all/answer_inline_query.rs index dd1b6a38..f680df36 100644 --- a/src/requests/all/answer_inline_query.rs +++ b/src/requests/all/answer_inline_query.rs @@ -17,13 +17,13 @@ use crate::{ pub struct AnswerInlineQuery { #[serde(skip_serializing)] bot: Bot, - inline_query_id: String, - results: Vec, - cache_time: Option, - is_personal: Option, - next_offset: Option, - switch_pm_text: Option, - switch_pm_parameter: Option, + pub inline_query_id: String, + pub results: Vec, + pub cache_time: Option, + pub is_personal: Option, + pub next_offset: Option, + pub switch_pm_text: Option, + pub switch_pm_parameter: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/answer_pre_checkout_query.rs b/src/requests/all/answer_pre_checkout_query.rs index 2ac4df42..d3ccc3d9 100644 --- a/src/requests/all/answer_pre_checkout_query.rs +++ b/src/requests/all/answer_pre_checkout_query.rs @@ -24,9 +24,9 @@ use crate::{ pub struct AnswerPreCheckoutQuery { #[serde(skip_serializing)] bot: Bot, - pre_checkout_query_id: String, - ok: bool, - error_message: Option, + pub pre_checkout_query_id: String, + pub ok: bool, + pub error_message: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/answer_shipping_query.rs b/src/requests/all/answer_shipping_query.rs index be4d6662..020dce24 100644 --- a/src/requests/all/answer_shipping_query.rs +++ b/src/requests/all/answer_shipping_query.rs @@ -20,10 +20,10 @@ use crate::{ pub struct AnswerShippingQuery { #[serde(skip_serializing)] bot: Bot, - shipping_query_id: String, - ok: bool, - shipping_options: Option>, - error_message: Option, + pub shipping_query_id: String, + pub ok: bool, + pub shipping_options: Option>, + pub error_message: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/create_new_sticker_set.rs b/src/requests/all/create_new_sticker_set.rs index d9c5e9da..9c2d1934 100644 --- a/src/requests/all/create_new_sticker_set.rs +++ b/src/requests/all/create_new_sticker_set.rs @@ -16,14 +16,14 @@ use crate::{ pub struct CreateNewStickerSet { #[serde(skip_serializing)] bot: Bot, - user_id: i32, - name: String, - title: String, + pub user_id: i32, + pub name: String, + pub title: String, #[serde(flatten)] - sticker_type: StickerType, - emojis: String, - contains_masks: Option, - mask_position: Option, + pub sticker_type: StickerType, + pub emojis: String, + pub contains_masks: Option, + pub mask_position: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/delete_chat_photo.rs b/src/requests/all/delete_chat_photo.rs index 01439bfa..56291e04 100644 --- a/src/requests/all/delete_chat_photo.rs +++ b/src/requests/all/delete_chat_photo.rs @@ -17,7 +17,7 @@ use crate::{ pub struct DeleteChatPhoto { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, + pub chat_id: ChatId, } #[async_trait::async_trait] diff --git a/src/requests/all/delete_chat_sticker_set.rs b/src/requests/all/delete_chat_sticker_set.rs index 9fe228c5..739aa0f6 100644 --- a/src/requests/all/delete_chat_sticker_set.rs +++ b/src/requests/all/delete_chat_sticker_set.rs @@ -22,7 +22,7 @@ use crate::{ pub struct DeleteChatStickerSet { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, + pub chat_id: ChatId, } #[async_trait::async_trait] diff --git a/src/requests/all/delete_message.rs b/src/requests/all/delete_message.rs index 35ead60a..4fd58ec4 100644 --- a/src/requests/all/delete_message.rs +++ b/src/requests/all/delete_message.rs @@ -27,8 +27,8 @@ use crate::{ pub struct DeleteMessage { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - message_id: i32, + pub chat_id: ChatId, + pub message_id: i32, } #[async_trait::async_trait] diff --git a/src/requests/all/delete_sticker_from_set.rs b/src/requests/all/delete_sticker_from_set.rs index 59269f7a..ce1df04c 100644 --- a/src/requests/all/delete_sticker_from_set.rs +++ b/src/requests/all/delete_sticker_from_set.rs @@ -15,7 +15,7 @@ use crate::{ pub struct DeleteStickerFromSet { #[serde(skip_serializing)] bot: Bot, - sticker: String, + pub sticker: String, } #[async_trait::async_trait] diff --git a/src/requests/all/edit_inline_message_caption.rs b/src/requests/all/edit_inline_message_caption.rs index e777f17c..4e4ff938 100644 --- a/src/requests/all/edit_inline_message_caption.rs +++ b/src/requests/all/edit_inline_message_caption.rs @@ -19,10 +19,10 @@ use crate::{ pub struct EditInlineMessageCaption { #[serde(skip_serializing)] bot: Bot, - inline_message_id: String, - caption: Option, - parse_mode: Option, - reply_markup: Option, + pub inline_message_id: String, + pub caption: Option, + pub parse_mode: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/edit_inline_message_live_location.rs b/src/requests/all/edit_inline_message_live_location.rs index 4b267427..eae40eb0 100644 --- a/src/requests/all/edit_inline_message_live_location.rs +++ b/src/requests/all/edit_inline_message_live_location.rs @@ -21,10 +21,10 @@ use crate::{ pub struct EditInlineMessageLiveLocation { #[serde(skip_serializing)] bot: Bot, - inline_message_id: String, - latitude: f32, - longitude: f32, - reply_markup: Option, + pub inline_message_id: String, + pub latitude: f32, + pub longitude: f32, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/edit_inline_message_media.rs b/src/requests/all/edit_inline_message_media.rs index ea0a4070..b874d5c4 100644 --- a/src/requests/all/edit_inline_message_media.rs +++ b/src/requests/all/edit_inline_message_media.rs @@ -24,9 +24,9 @@ use crate::{ pub struct EditInlineMessageMedia { #[serde(skip_serializing)] bot: Bot, - inline_message_id: String, - media: InputMedia, - reply_markup: Option, + pub inline_message_id: String, + pub media: InputMedia, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/edit_inline_message_reply_markup.rs b/src/requests/all/edit_inline_message_reply_markup.rs index cd96c686..632bac1d 100644 --- a/src/requests/all/edit_inline_message_reply_markup.rs +++ b/src/requests/all/edit_inline_message_reply_markup.rs @@ -20,8 +20,8 @@ use crate::{ pub struct EditInlineMessageReplyMarkup { #[serde(skip_serializing)] bot: Bot, - inline_message_id: String, - reply_markup: Option, + pub inline_message_id: String, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/edit_inline_message_text.rs b/src/requests/all/edit_inline_message_text.rs index 2b266314..a4716ca0 100644 --- a/src/requests/all/edit_inline_message_text.rs +++ b/src/requests/all/edit_inline_message_text.rs @@ -19,11 +19,11 @@ use crate::{ pub struct EditInlineMessageText { #[serde(skip_serializing)] bot: Bot, - inline_message_id: String, - text: String, - parse_mode: Option, - disable_web_page_preview: Option, - reply_markup: Option, + pub inline_message_id: String, + pub text: String, + pub parse_mode: Option, + pub disable_web_page_preview: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/edit_message_caption.rs b/src/requests/all/edit_message_caption.rs index 9294af93..441f4931 100644 --- a/src/requests/all/edit_message_caption.rs +++ b/src/requests/all/edit_message_caption.rs @@ -19,11 +19,11 @@ use crate::{ pub struct EditMessageCaption { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - message_id: i32, - caption: Option, - parse_mode: Option, - reply_markup: Option, + pub chat_id: ChatId, + pub message_id: i32, + pub caption: Option, + pub parse_mode: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/edit_message_live_location.rs b/src/requests/all/edit_message_live_location.rs index c5afd295..7d1f4779 100644 --- a/src/requests/all/edit_message_live_location.rs +++ b/src/requests/all/edit_message_live_location.rs @@ -21,11 +21,11 @@ use crate::{ pub struct EditMessageLiveLocation { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - message_id: i32, - latitude: f32, - longitude: f32, - reply_markup: Option, + pub chat_id: ChatId, + pub message_id: i32, + pub latitude: f32, + pub longitude: f32, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/edit_message_media.rs b/src/requests/all/edit_message_media.rs index 826244db..36080a96 100644 --- a/src/requests/all/edit_message_media.rs +++ b/src/requests/all/edit_message_media.rs @@ -22,10 +22,10 @@ use crate::{ pub struct EditMessageMedia { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - message_id: i32, - media: InputMedia, - reply_markup: Option, + pub chat_id: ChatId, + pub message_id: i32, + pub media: InputMedia, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/edit_message_reply_markup.rs b/src/requests/all/edit_message_reply_markup.rs index 66aab5bf..45f8f14d 100644 --- a/src/requests/all/edit_message_reply_markup.rs +++ b/src/requests/all/edit_message_reply_markup.rs @@ -19,9 +19,9 @@ use crate::{ pub struct EditMessageReplyMarkup { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - message_id: i32, - reply_markup: Option, + pub chat_id: ChatId, + pub message_id: i32, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/edit_message_text.rs b/src/requests/all/edit_message_text.rs index 12251313..4fcc0442 100644 --- a/src/requests/all/edit_message_text.rs +++ b/src/requests/all/edit_message_text.rs @@ -19,12 +19,12 @@ use crate::{ pub struct EditMessageText { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - message_id: i32, - text: String, - parse_mode: Option, - disable_web_page_preview: Option, - reply_markup: Option, + pub chat_id: ChatId, + pub message_id: i32, + pub text: String, + pub parse_mode: Option, + pub disable_web_page_preview: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/export_chat_invite_link.rs b/src/requests/all/export_chat_invite_link.rs index 51a26437..70a5825a 100644 --- a/src/requests/all/export_chat_invite_link.rs +++ b/src/requests/all/export_chat_invite_link.rs @@ -31,7 +31,7 @@ use crate::{ pub struct ExportChatInviteLink { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, + pub chat_id: ChatId, } #[async_trait::async_trait] diff --git a/src/requests/all/forward_message.rs b/src/requests/all/forward_message.rs index feb9ec02..24783cdc 100644 --- a/src/requests/all/forward_message.rs +++ b/src/requests/all/forward_message.rs @@ -15,10 +15,10 @@ use crate::{ pub struct ForwardMessage { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - from_chat_id: ChatId, - disable_notification: Option, - message_id: i32, + pub chat_id: ChatId, + pub from_chat_id: ChatId, + pub disable_notification: Option, + pub message_id: i32, } #[async_trait::async_trait] diff --git a/src/requests/all/get_chat.rs b/src/requests/all/get_chat.rs index 11406776..e0552798 100644 --- a/src/requests/all/get_chat.rs +++ b/src/requests/all/get_chat.rs @@ -17,7 +17,7 @@ use crate::{ pub struct GetChat { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, + pub chat_id: ChatId, } #[async_trait::async_trait] diff --git a/src/requests/all/get_chat_administrators.rs b/src/requests/all/get_chat_administrators.rs index 06657ec5..44cb9c53 100644 --- a/src/requests/all/get_chat_administrators.rs +++ b/src/requests/all/get_chat_administrators.rs @@ -18,7 +18,7 @@ use crate::{ pub struct GetChatAdministrators { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, + pub chat_id: ChatId, } #[async_trait::async_trait] diff --git a/src/requests/all/get_chat_member.rs b/src/requests/all/get_chat_member.rs index 4ebe3f56..5883eea3 100644 --- a/src/requests/all/get_chat_member.rs +++ b/src/requests/all/get_chat_member.rs @@ -15,8 +15,8 @@ use crate::{ pub struct GetChatMember { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - user_id: i32, + pub chat_id: ChatId, + pub user_id: i32, } #[async_trait::async_trait] diff --git a/src/requests/all/get_chat_members_count.rs b/src/requests/all/get_chat_members_count.rs index 5c78596b..c9861215 100644 --- a/src/requests/all/get_chat_members_count.rs +++ b/src/requests/all/get_chat_members_count.rs @@ -15,7 +15,7 @@ use crate::{ pub struct GetChatMembersCount { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, + pub chat_id: ChatId, } #[async_trait::async_trait] diff --git a/src/requests/all/get_file.rs b/src/requests/all/get_file.rs index d2d0556c..d45b6013 100644 --- a/src/requests/all/get_file.rs +++ b/src/requests/all/get_file.rs @@ -31,7 +31,7 @@ use crate::{ pub struct GetFile { #[serde(skip_serializing)] bot: Bot, - file_id: String, + pub file_id: String, } #[async_trait::async_trait] diff --git a/src/requests/all/get_game_high_scores.rs b/src/requests/all/get_game_high_scores.rs index 1a43d63e..e6d325e0 100644 --- a/src/requests/all/get_game_high_scores.rs +++ b/src/requests/all/get_game_high_scores.rs @@ -24,8 +24,8 @@ pub struct GetGameHighScores { #[serde(skip_serializing)] bot: Bot, #[serde(flatten)] - target: TargetMessage, - user_id: i32, + pub target: TargetMessage, + pub user_id: i32, } #[async_trait::async_trait] diff --git a/src/requests/all/get_sticker_set.rs b/src/requests/all/get_sticker_set.rs index a8a83bea..185d721a 100644 --- a/src/requests/all/get_sticker_set.rs +++ b/src/requests/all/get_sticker_set.rs @@ -15,7 +15,7 @@ use crate::{ pub struct GetStickerSet { #[serde(skip_serializing)] bot: Bot, - name: String, + pub name: String, } #[async_trait::async_trait] diff --git a/src/requests/all/get_updates.rs b/src/requests/all/get_updates.rs index a9f4558d..9cb57a7a 100644 --- a/src/requests/all/get_updates.rs +++ b/src/requests/all/get_updates.rs @@ -22,10 +22,10 @@ use crate::{ pub struct GetUpdates { #[serde(skip_serializing)] pub(crate) bot: Bot, - pub(crate) offset: Option, - pub(crate) limit: Option, - pub(crate) timeout: Option, - pub(crate) allowed_updates: Option>, + pub offset: Option, + pub limit: Option, + pub timeout: Option, + pub allowed_updates: Option>, } #[async_trait::async_trait] diff --git a/src/requests/all/get_user_profile_photos.rs b/src/requests/all/get_user_profile_photos.rs index 663a07a4..a3105701 100644 --- a/src/requests/all/get_user_profile_photos.rs +++ b/src/requests/all/get_user_profile_photos.rs @@ -15,9 +15,9 @@ use crate::{ pub struct GetUserProfilePhotos { #[serde(skip_serializing)] bot: Bot, - user_id: i32, - offset: Option, - limit: Option, + pub user_id: i32, + pub offset: Option, + pub limit: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/kick_chat_member.rs b/src/requests/all/kick_chat_member.rs index 31953949..50c5bb04 100644 --- a/src/requests/all/kick_chat_member.rs +++ b/src/requests/all/kick_chat_member.rs @@ -22,9 +22,9 @@ use crate::{ pub struct KickChatMember { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - user_id: i32, - until_date: Option, + pub chat_id: ChatId, + pub user_id: i32, + pub until_date: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/leave_chat.rs b/src/requests/all/leave_chat.rs index d0411efb..1290fa5c 100644 --- a/src/requests/all/leave_chat.rs +++ b/src/requests/all/leave_chat.rs @@ -15,7 +15,7 @@ use crate::{ pub struct LeaveChat { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, + pub chat_id: ChatId, } #[async_trait::async_trait] diff --git a/src/requests/all/pin_chat_message.rs b/src/requests/all/pin_chat_message.rs index ae1f3227..9a0655b1 100644 --- a/src/requests/all/pin_chat_message.rs +++ b/src/requests/all/pin_chat_message.rs @@ -19,9 +19,9 @@ use crate::{ pub struct PinChatMessage { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - message_id: i32, - disable_notification: Option, + pub chat_id: ChatId, + pub message_id: i32, + pub disable_notification: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/promote_chat_member.rs b/src/requests/all/promote_chat_member.rs index 468b3c48..cc1beef6 100644 --- a/src/requests/all/promote_chat_member.rs +++ b/src/requests/all/promote_chat_member.rs @@ -19,16 +19,16 @@ use crate::{ pub struct PromoteChatMember { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - user_id: i32, - can_change_info: Option, - can_post_messages: Option, - can_edit_messages: Option, - can_delete_messages: Option, - can_invite_users: Option, - can_restrict_members: Option, - can_pin_messages: Option, - can_promote_members: Option, + pub chat_id: ChatId, + pub user_id: i32, + pub can_change_info: Option, + pub can_post_messages: Option, + pub can_edit_messages: Option, + pub can_delete_messages: Option, + pub can_invite_users: Option, + pub can_restrict_members: Option, + pub can_pin_messages: Option, + pub can_promote_members: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/restrict_chat_member.rs b/src/requests/all/restrict_chat_member.rs index 6b825b91..5ce1ebab 100644 --- a/src/requests/all/restrict_chat_member.rs +++ b/src/requests/all/restrict_chat_member.rs @@ -19,10 +19,10 @@ use crate::{ pub struct RestrictChatMember { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - user_id: i32, - permissions: ChatPermissions, - until_date: Option, + pub chat_id: ChatId, + pub user_id: i32, + pub permissions: ChatPermissions, + pub until_date: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/send_audio.rs b/src/requests/all/send_audio.rs index dc61458c..125ed8c0 100644 --- a/src/requests/all/send_audio.rs +++ b/src/requests/all/send_audio.rs @@ -23,17 +23,17 @@ use crate::{ pub struct SendAudio { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - audio: InputFile, - caption: Option, - parse_mode: Option, - duration: Option, - performer: Option, - title: Option, - thumb: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, + pub chat_id: ChatId, + pub audio: InputFile, + pub caption: Option, + pub parse_mode: Option, + pub duration: Option, + pub performer: Option, + pub title: Option, + pub thumb: Option, + pub disable_notification: Option, + pub reply_to_message_id: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/send_chat_action.rs b/src/requests/all/send_chat_action.rs index 5fe853b2..82c6328e 100644 --- a/src/requests/all/send_chat_action.rs +++ b/src/requests/all/send_chat_action.rs @@ -29,8 +29,8 @@ use crate::{ pub struct SendChatAction { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - action: SendChatActionKind, + pub chat_id: ChatId, + pub action: SendChatActionKind, } /// A type of action used in [`SendChatAction`]. diff --git a/src/requests/all/send_contact.rs b/src/requests/all/send_contact.rs index ef3ff034..61b59e4d 100644 --- a/src/requests/all/send_contact.rs +++ b/src/requests/all/send_contact.rs @@ -15,14 +15,14 @@ use crate::{ pub struct SendContact { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - phone_number: String, - first_name: String, - last_name: Option, - vcard: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, + pub chat_id: ChatId, + pub phone_number: String, + pub first_name: String, + pub last_name: Option, + pub vcard: Option, + pub disable_notification: Option, + pub reply_to_message_id: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/send_dice.rs b/src/requests/all/send_dice.rs index 663d7001..ad3cb16a 100644 --- a/src/requests/all/send_dice.rs +++ b/src/requests/all/send_dice.rs @@ -15,13 +15,12 @@ use crate::{ pub struct SendDice { #[serde(skip_serializing)] bot: Bot, - - chat_id: ChatId, + pub chat_id: ChatId, #[serde(flatten)] - emoji: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, + pub emoji: Option, + pub disable_notification: Option, + pub reply_to_message_id: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/send_document.rs b/src/requests/all/send_document.rs index e865a008..7357341d 100644 --- a/src/requests/all/send_document.rs +++ b/src/requests/all/send_document.rs @@ -18,14 +18,14 @@ use crate::{ pub struct SendDocument { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - document: InputFile, - thumb: Option, - caption: Option, - parse_mode: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, + pub chat_id: ChatId, + pub document: InputFile, + pub thumb: Option, + pub caption: Option, + pub parse_mode: Option, + pub disable_notification: Option, + pub reply_to_message_id: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/send_game.rs b/src/requests/all/send_game.rs index 03a0b0e2..876cabba 100644 --- a/src/requests/all/send_game.rs +++ b/src/requests/all/send_game.rs @@ -15,11 +15,11 @@ use crate::{ pub struct SendGame { #[serde(skip_serializing)] bot: Bot, - chat_id: i32, - game_short_name: String, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, + pub chat_id: i32, + pub game_short_name: String, + pub disable_notification: Option, + pub reply_to_message_id: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/send_invoice.rs b/src/requests/all/send_invoice.rs index 8466cdba..0469e25f 100644 --- a/src/requests/all/send_invoice.rs +++ b/src/requests/all/send_invoice.rs @@ -15,29 +15,29 @@ use crate::{ pub struct SendInvoice { #[serde(skip_serializing)] bot: Bot, - chat_id: i32, - title: String, - description: String, - payload: String, - provider_token: String, - start_parameter: String, - currency: String, - prices: Vec, - provider_data: Option, - photo_url: Option, - photo_size: Option, - photo_width: Option, - photo_height: Option, - need_name: Option, - need_phone_number: Option, - need_email: Option, - need_shipping_address: Option, - send_phone_number_to_provider: Option, - send_email_to_provider: Option, - is_flexible: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, + pub chat_id: i32, + pub title: String, + pub description: String, + pub payload: String, + pub provider_token: String, + pub start_parameter: String, + pub currency: String, + pub prices: Vec, + pub provider_data: Option, + pub photo_url: Option, + pub photo_size: Option, + pub photo_width: Option, + pub photo_height: Option, + pub need_name: Option, + pub need_phone_number: Option, + pub need_email: Option, + pub need_shipping_address: Option, + pub send_phone_number_to_provider: Option, + pub send_email_to_provider: Option, + pub is_flexible: Option, + pub disable_notification: Option, + pub reply_to_message_id: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/send_location.rs b/src/requests/all/send_location.rs index 20201ad1..a55d3cc5 100644 --- a/src/requests/all/send_location.rs +++ b/src/requests/all/send_location.rs @@ -15,13 +15,13 @@ use crate::{ pub struct SendLocation { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - latitude: f32, - longitude: f32, - live_period: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, + pub chat_id: ChatId, + pub latitude: f32, + pub longitude: f32, + pub live_period: Option, + pub disable_notification: Option, + pub reply_to_message_id: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/send_media_group.rs b/src/requests/all/send_media_group.rs index e7adb68a..2c9f422a 100644 --- a/src/requests/all/send_media_group.rs +++ b/src/requests/all/send_media_group.rs @@ -15,10 +15,10 @@ use crate::{ pub struct SendMediaGroup { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - media: Vec, // TODO: InputMediaPhoto and InputMediaVideo - disable_notification: Option, - reply_to_message_id: Option, + pub chat_id: ChatId, + pub media: Vec, // TODO: InputMediaPhoto and InputMediaVideo + pub disable_notification: Option, + pub reply_to_message_id: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/send_photo.rs b/src/requests/all/send_photo.rs index 42ad8e6f..e6126fcf 100644 --- a/src/requests/all/send_photo.rs +++ b/src/requests/all/send_photo.rs @@ -15,13 +15,13 @@ use crate::{ pub struct SendPhoto { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - photo: InputFile, - caption: Option, - parse_mode: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, + pub chat_id: ChatId, + pub photo: InputFile, + pub caption: Option, + pub parse_mode: Option, + pub disable_notification: Option, + pub reply_to_message_id: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/send_poll.rs b/src/requests/all/send_poll.rs index 3dd412a1..699362c9 100644 --- a/src/requests/all/send_poll.rs +++ b/src/requests/all/send_poll.rs @@ -15,21 +15,21 @@ use crate::{ pub struct SendPoll { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - question: String, - options: Vec, - is_anonymous: Option, - poll_type: Option, - allows_multiple_answers: Option, - correct_option_id: Option, - explanation: Option, - explanation_parse_mode: Option, - open_period: Option, - close_date: Option, - is_closed: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, + pub chat_id: ChatId, + pub question: String, + pub options: Vec, + pub is_anonymous: Option, + pub poll_type: Option, + pub allows_multiple_answers: Option, + pub correct_option_id: Option, + pub explanation: Option, + pub explanation_parse_mode: Option, + pub open_period: Option, + pub close_date: Option, + pub is_closed: Option, + pub disable_notification: Option, + pub reply_to_message_id: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/send_sticker.rs b/src/requests/all/send_sticker.rs index dd25c9bb..8c9f0b00 100644 --- a/src/requests/all/send_sticker.rs +++ b/src/requests/all/send_sticker.rs @@ -17,11 +17,11 @@ use crate::{ pub struct SendSticker { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - sticker: InputFile, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, + pub chat_id: ChatId, + pub sticker: InputFile, + pub disable_notification: Option, + pub reply_to_message_id: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/send_venue.rs b/src/requests/all/send_venue.rs index 513ece7d..6dacb74e 100644 --- a/src/requests/all/send_venue.rs +++ b/src/requests/all/send_venue.rs @@ -15,16 +15,16 @@ use crate::{ pub struct SendVenue { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - latitude: f32, - longitude: f32, - title: String, - address: String, - foursquare_id: Option, - foursquare_type: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, + pub chat_id: ChatId, + pub latitude: f32, + pub longitude: f32, + pub title: String, + pub address: String, + pub foursquare_id: Option, + pub foursquare_type: Option, + pub disable_notification: Option, + pub reply_to_message_id: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/send_video.rs b/src/requests/all/send_video.rs index 3f218f10..4c84e52d 100644 --- a/src/requests/all/send_video.rs +++ b/src/requests/all/send_video.rs @@ -19,18 +19,18 @@ use crate::{ pub struct SendVideo { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - video: InputFile, - duration: Option, - width: Option, - height: Option, - thumb: Option, - caption: Option, - parse_mode: Option, - supports_streaming: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, + pub chat_id: ChatId, + pub video: InputFile, + pub duration: Option, + pub width: Option, + pub height: Option, + pub thumb: Option, + pub caption: Option, + pub parse_mode: Option, + pub supports_streaming: Option, + pub disable_notification: Option, + pub reply_to_message_id: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/send_video_note.rs b/src/requests/all/send_video_note.rs index 9a0e07dd..7ae4ae82 100644 --- a/src/requests/all/send_video_note.rs +++ b/src/requests/all/send_video_note.rs @@ -18,14 +18,14 @@ use crate::{ pub struct SendVideoNote { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - video_note: InputFile, - duration: Option, - length: Option, - thumb: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, + pub chat_id: ChatId, + pub video_note: InputFile, + pub duration: Option, + pub length: Option, + pub thumb: Option, + pub disable_notification: Option, + pub reply_to_message_id: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/send_voice.rs b/src/requests/all/send_voice.rs index 6bfc301a..20ca93c2 100644 --- a/src/requests/all/send_voice.rs +++ b/src/requests/all/send_voice.rs @@ -24,14 +24,14 @@ use crate::{ pub struct SendVoice { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - voice: InputFile, - caption: Option, - parse_mode: Option, - duration: Option, - disable_notification: Option, - reply_to_message_id: Option, - reply_markup: Option, + pub chat_id: ChatId, + pub voice: InputFile, + pub caption: Option, + pub parse_mode: Option, + pub duration: Option, + pub disable_notification: Option, + pub reply_to_message_id: Option, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/set_chat_administrator_custom_title.rs b/src/requests/all/set_chat_administrator_custom_title.rs index a3211811..bbc7c251 100644 --- a/src/requests/all/set_chat_administrator_custom_title.rs +++ b/src/requests/all/set_chat_administrator_custom_title.rs @@ -16,9 +16,9 @@ use crate::{ pub struct SetChatAdministratorCustomTitle { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - user_id: i32, - custom_title: String, + pub chat_id: ChatId, + pub user_id: i32, + pub custom_title: String, } #[async_trait::async_trait] diff --git a/src/requests/all/set_chat_description.rs b/src/requests/all/set_chat_description.rs index 092c495b..a9c98bc9 100644 --- a/src/requests/all/set_chat_description.rs +++ b/src/requests/all/set_chat_description.rs @@ -19,8 +19,8 @@ use crate::{ pub struct SetChatDescription { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - description: Option, + pub chat_id: ChatId, + pub description: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/set_chat_permissions.rs b/src/requests/all/set_chat_permissions.rs index aaa1881b..b2233f9bc 100644 --- a/src/requests/all/set_chat_permissions.rs +++ b/src/requests/all/set_chat_permissions.rs @@ -18,8 +18,8 @@ use crate::{ pub struct SetChatPermissions { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - permissions: ChatPermissions, + pub chat_id: ChatId, + pub permissions: ChatPermissions, } #[async_trait::async_trait] diff --git a/src/requests/all/set_chat_photo.rs b/src/requests/all/set_chat_photo.rs index dcc5febc..1572cc03 100644 --- a/src/requests/all/set_chat_photo.rs +++ b/src/requests/all/set_chat_photo.rs @@ -18,8 +18,8 @@ use crate::{ pub struct SetChatPhoto { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - photo: InputFile, + pub chat_id: ChatId, + pub photo: InputFile, } #[async_trait::async_trait] diff --git a/src/requests/all/set_chat_sticker_set.rs b/src/requests/all/set_chat_sticker_set.rs index 010a10f5..04b18e61 100644 --- a/src/requests/all/set_chat_sticker_set.rs +++ b/src/requests/all/set_chat_sticker_set.rs @@ -19,8 +19,8 @@ use crate::{ pub struct SetChatStickerSet { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - sticker_set_name: String, + pub chat_id: ChatId, + pub sticker_set_name: String, } #[async_trait::async_trait] diff --git a/src/requests/all/set_chat_title.rs b/src/requests/all/set_chat_title.rs index 7d6ae6d1..3b0e92c8 100644 --- a/src/requests/all/set_chat_title.rs +++ b/src/requests/all/set_chat_title.rs @@ -18,8 +18,8 @@ use crate::{ pub struct SetChatTitle { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - title: String, + pub chat_id: ChatId, + pub title: String, } #[async_trait::async_trait] diff --git a/src/requests/all/set_game_score.rs b/src/requests/all/set_game_score.rs index 8e429e3a..5a467fe9 100644 --- a/src/requests/all/set_game_score.rs +++ b/src/requests/all/set_game_score.rs @@ -24,11 +24,11 @@ pub struct SetGameScore { #[serde(skip_serializing)] bot: Bot, #[serde(flatten)] - target: TargetMessage, - user_id: i32, - score: i32, - force: Option, - disable_edit_message: Option, + pub target: TargetMessage, + pub user_id: i32, + pub score: i32, + pub force: Option, + pub disable_edit_message: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/set_my_commands.rs b/src/requests/all/set_my_commands.rs index 831f9705..c2447c67 100644 --- a/src/requests/all/set_my_commands.rs +++ b/src/requests/all/set_my_commands.rs @@ -15,8 +15,7 @@ use crate::{ pub struct SetMyCommands { #[serde(skip_serializing)] bot: Bot, - - commands: Vec, + pub commands: Vec, } #[async_trait::async_trait] diff --git a/src/requests/all/set_sticker_position_in_set.rs b/src/requests/all/set_sticker_position_in_set.rs index 4ed78551..6ca7e701 100644 --- a/src/requests/all/set_sticker_position_in_set.rs +++ b/src/requests/all/set_sticker_position_in_set.rs @@ -16,8 +16,8 @@ use crate::{ pub struct SetStickerPositionInSet { #[serde(skip_serializing)] bot: Bot, - sticker: String, - position: i32, + pub sticker: String, + pub position: i32, } #[async_trait::async_trait] diff --git a/src/requests/all/set_sticker_set_thumb.rs b/src/requests/all/set_sticker_set_thumb.rs index 1f84aacd..343f270a 100644 --- a/src/requests/all/set_sticker_set_thumb.rs +++ b/src/requests/all/set_sticker_set_thumb.rs @@ -16,9 +16,9 @@ use crate::{ pub struct SetStickerSetThumb { #[serde(skip_serializing)] bot: Bot, - name: String, - user_id: i32, - thumb: Option, + pub name: String, + pub user_id: i32, + pub thumb: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/set_webhook.rs b/src/requests/all/set_webhook.rs index c2aff792..4a1f565a 100644 --- a/src/requests/all/set_webhook.rs +++ b/src/requests/all/set_webhook.rs @@ -28,10 +28,10 @@ use crate::{ pub struct SetWebhook { #[serde(skip_serializing)] bot: Bot, - url: String, - certificate: Option, - max_connections: Option, - allowed_updates: Option>, + pub url: String, + pub certificate: Option, + pub max_connections: Option, + pub allowed_updates: Option>, } #[async_trait::async_trait] diff --git a/src/requests/all/stop_inline_message_live_location.rs b/src/requests/all/stop_inline_message_live_location.rs index 40fb5604..55c23038 100644 --- a/src/requests/all/stop_inline_message_live_location.rs +++ b/src/requests/all/stop_inline_message_live_location.rs @@ -20,8 +20,8 @@ use crate::{ pub struct StopInlineMessageLiveLocation { #[serde(skip_serializing)] bot: Bot, - inline_message_id: String, - reply_markup: Option, + pub inline_message_id: String, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/stop_message_live_location.rs b/src/requests/all/stop_message_live_location.rs index 892492ea..e07db923 100644 --- a/src/requests/all/stop_message_live_location.rs +++ b/src/requests/all/stop_message_live_location.rs @@ -20,9 +20,9 @@ use crate::{ pub struct StopMessageLiveLocation { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - message_id: i32, - reply_markup: Option, + pub chat_id: ChatId, + pub message_id: i32, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/stop_poll.rs b/src/requests/all/stop_poll.rs index 946b4760..c60bdb50 100644 --- a/src/requests/all/stop_poll.rs +++ b/src/requests/all/stop_poll.rs @@ -15,9 +15,9 @@ use crate::{ pub struct StopPoll { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - message_id: i32, - reply_markup: Option, + pub chat_id: ChatId, + pub message_id: i32, + pub reply_markup: Option, } #[async_trait::async_trait] diff --git a/src/requests/all/unban_chat_member.rs b/src/requests/all/unban_chat_member.rs index 5aee625c..ba206733 100644 --- a/src/requests/all/unban_chat_member.rs +++ b/src/requests/all/unban_chat_member.rs @@ -18,8 +18,8 @@ use crate::{ pub struct UnbanChatMember { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, - user_id: i32, + pub chat_id: ChatId, + pub user_id: i32, } #[async_trait::async_trait] diff --git a/src/requests/all/unpin_chat_message.rs b/src/requests/all/unpin_chat_message.rs index 15e76a99..1ce74c30 100644 --- a/src/requests/all/unpin_chat_message.rs +++ b/src/requests/all/unpin_chat_message.rs @@ -19,7 +19,7 @@ use crate::{ pub struct UnpinChatMessage { #[serde(skip_serializing)] bot: Bot, - chat_id: ChatId, + pub chat_id: ChatId, } #[async_trait::async_trait] diff --git a/src/requests/all/upload_sticker_file.rs b/src/requests/all/upload_sticker_file.rs index b09b143f..9f5df548 100644 --- a/src/requests/all/upload_sticker_file.rs +++ b/src/requests/all/upload_sticker_file.rs @@ -20,8 +20,8 @@ use crate::{ pub struct UploadStickerFile { #[serde(skip_serializing)] bot: Bot, - user_id: i32, - png_sticker: InputFile, + pub user_id: i32, + pub png_sticker: InputFile, } #[async_trait::async_trait] impl Request for UploadStickerFile { From fd64e17f4ce56850b287d9b04a831df65e23542c Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 16 Aug 2020 21:22:25 +0300 Subject: [PATCH 085/755] remove all `#[non_exhaustive]`s --- src/types/allowed_update.rs | 1 - src/types/animation.rs | 1 - src/types/audio.rs | 1 - src/types/bot_command.rs | 1 - src/types/callback_game.rs | 1 - src/types/callback_query.rs | 1 - src/types/chat.rs | 8 ---- src/types/chat_action.rs | 1 - src/types/chat_id.rs | 1 - src/types/chat_member.rs | 1 - src/types/chat_permissions.rs | 1 - src/types/chat_photo.rs | 1 - src/types/chosen_inline_result.rs | 1 - src/types/contact.rs | 1 - src/types/dice.rs | 1 - src/types/document.rs | 1 - src/types/encrypted_credentials.rs | 1 - src/types/encrypted_passport_element.rs | 15 -------- src/types/file.rs | 1 - src/types/force_reply.rs | 1 - src/types/game.rs | 1 - src/types/game_high_score.rs | 1 - src/types/inline_keyboard_button.rs | 2 - src/types/inline_keyboard_markup.rs | 1 - src/types/inline_query.rs | 1 - src/types/inline_query_result.rs | 1 - src/types/inline_query_result_article.rs | 1 - src/types/inline_query_result_audio.rs | 1 - src/types/inline_query_result_cached_audio.rs | 1 - .../inline_query_result_cached_document.rs | 1 - src/types/inline_query_result_cached_gif.rs | 1 - .../inline_query_result_cached_mpeg4_gif.rs | 1 - src/types/inline_query_result_cached_photo.rs | 1 - .../inline_query_result_cached_sticker.rs | 1 - src/types/inline_query_result_cached_video.rs | 1 - src/types/inline_query_result_cached_voice.rs | 1 - src/types/inline_query_result_contact.rs | 1 - src/types/inline_query_result_document.rs | 1 - src/types/inline_query_result_game.rs | 1 - src/types/inline_query_result_gif.rs | 1 - src/types/inline_query_result_location.rs | 1 - src/types/inline_query_result_mpeg4_gif.rs | 1 - src/types/inline_query_result_photo.rs | 1 - src/types/inline_query_result_venue.rs | 1 - src/types/inline_query_result_video.rs | 1 - src/types/inline_query_result_voice.rs | 1 - src/types/input_file.rs | 1 - src/types/input_media.rs | 6 --- src/types/input_message_content.rs | 5 --- src/types/invoice.rs | 1 - src/types/keyboard_button.rs | 3 -- src/types/keyboard_button_poll_type.rs | 1 - src/types/label_price.rs | 1 - src/types/location.rs | 1 - src/types/login_url.rs | 1 - src/types/mask_position.rs | 1 - src/types/me.rs | 1 - src/types/message.rs | 38 ------------------- src/types/message_entity.rs | 2 - src/types/order_info.rs | 1 - src/types/parse_mode.rs | 1 - src/types/passport_data.rs | 1 - src/types/passport_element_error.rs | 20 ---------- src/types/passport_file.rs | 1 - src/types/photo_size.rs | 1 - src/types/poll.rs | 2 - src/types/poll_answer.rs | 1 - src/types/poll_type.rs | 1 - src/types/pre_checkout_query.rs | 1 - src/types/reply_keyboard_markup.rs | 1 - src/types/reply_keyboard_remove.rs | 1 - src/types/reply_markup.rs | 1 - src/types/response_parameters.rs | 1 - src/types/send_invoice.rs | 1 - src/types/shipping_address.rs | 1 - src/types/shipping_option.rs | 1 - src/types/shipping_query.rs | 1 - src/types/sticker.rs | 1 - src/types/sticker_set.rs | 1 - src/types/sticker_type.rs | 1 - src/types/successful_payment.rs | 1 - src/types/update.rs | 2 - src/types/user.rs | 1 - src/types/user_profile_photos.rs | 1 - src/types/venue.rs | 1 - src/types/video.rs | 1 - src/types/video_note.rs | 1 - src/types/voice.rs | 1 - src/types/webhook_info.rs | 1 - 89 files changed, 181 deletions(-) diff --git a/src/types/allowed_update.rs b/src/types/allowed_update.rs index f3b920ac..932f053e 100644 --- a/src/types/allowed_update.rs +++ b/src/types/allowed_update.rs @@ -2,7 +2,6 @@ use serde::{Deserialize, Serialize}; #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -#[non_exhaustive] pub enum AllowedUpdate { Message, EditedMessage, diff --git a/src/types/animation.rs b/src/types/animation.rs index dc52901c..9cf1b0e5 100644 --- a/src/types/animation.rs +++ b/src/types/animation.rs @@ -8,7 +8,6 @@ use crate::types::{MimeWrapper, PhotoSize}; /// [The official docs](https://core.telegram.org/bots/api#animation). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct Animation { /// An identifier for this file. pub file_id: String, diff --git a/src/types/audio.rs b/src/types/audio.rs index d5599c20..b9d6279d 100644 --- a/src/types/audio.rs +++ b/src/types/audio.rs @@ -8,7 +8,6 @@ use crate::types::{MimeWrapper, PhotoSize}; /// [The official docs](https://core.telegram.org/bots/api#audio). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct Audio { /// An identifier for this file. pub file_id: String, diff --git a/src/types/bot_command.rs b/src/types/bot_command.rs index d3116dff..cb6d9c6e 100644 --- a/src/types/bot_command.rs +++ b/src/types/bot_command.rs @@ -4,7 +4,6 @@ use serde::{Deserialize, Serialize}; /// /// [The official docs](https://core.telegram.org/bots/api#botcommand). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct BotCommand { /// Text of the command, 1-32 characters. /// diff --git a/src/types/callback_game.rs b/src/types/callback_game.rs index 5b2d6e0f..0fd9a9b0 100644 --- a/src/types/callback_game.rs +++ b/src/types/callback_game.rs @@ -9,5 +9,4 @@ use serde::{Deserialize, Serialize}; /// /// [@Botfather]: https://t.me/botfather #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct CallbackGame; diff --git a/src/types/callback_query.rs b/src/types/callback_query.rs index d8c1d07c..611c23c1 100644 --- a/src/types/callback_query.rs +++ b/src/types/callback_query.rs @@ -17,7 +17,6 @@ use crate::types::{Message, User}; /// [inline mode]: https://core.telegram.org/bots/api#inline-mode #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct CallbackQuery { /// An unique identifier for this query. pub id: String, diff --git a/src/types/chat.rs b/src/types/chat.rs index c278d415..2623614a 100644 --- a/src/types/chat.rs +++ b/src/types/chat.rs @@ -7,7 +7,6 @@ use crate::types::{ChatPermissions, ChatPhoto, Message}; /// [The official docs](https://core.telegram.org/bots/api#chat). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct Chat { /// A unique identifier for this chat. This number may be greater than 32 /// bits and some programming languages may have difficulty/silent defects @@ -49,7 +48,6 @@ impl Chat { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(untagged)] -#[non_exhaustive] pub enum ChatKind { Public(ChatPublic), Private(ChatPrivate), @@ -57,7 +55,6 @@ pub enum ChatKind { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct ChatPublic { /// A title, for supergroups, channels and group chats. pub title: Option, @@ -127,7 +124,6 @@ impl ChatPublic { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct ChatPrivate { /// A dummy field. Used to ensure that the `type` field is equal to /// `private`. @@ -180,7 +176,6 @@ impl ChatPrivate { #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] #[serde(tag = "type")] -#[non_exhaustive] pub enum PublicChatKind { Channel(PublicChatChannel), Group(PublicChatGroup), @@ -189,7 +184,6 @@ pub enum PublicChatKind { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Default, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PublicChatChannel { /// A username, for private chats, supergroups and channels if available. pub username: Option, @@ -203,7 +197,6 @@ impl PublicChatChannel { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Default, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PublicChatGroup { /// A default chat member permissions, for groups and supergroups. Returned /// only from [`Bot::get_chat`]. @@ -220,7 +213,6 @@ impl PublicChatGroup { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Default, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PublicChatSupergroup { /// A username, for private chats, supergroups and channels if /// available. diff --git a/src/types/chat_action.rs b/src/types/chat_action.rs index 9c112a56..a73887ea 100644 --- a/src/types/chat_action.rs +++ b/src/types/chat_action.rs @@ -2,7 +2,6 @@ use serde::{Deserialize, Serialize}; #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -#[non_exhaustive] pub enum ChatAction { Typing, UploadPhoto, diff --git a/src/types/chat_id.rs b/src/types/chat_id.rs index f5723da0..f4e57e98 100644 --- a/src/types/chat_id.rs +++ b/src/types/chat_id.rs @@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize}; /// (in the format `@channelusername`). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Display, From)] #[serde(untagged)] -#[non_exhaustive] pub enum ChatId { /// A chat identifier. #[display(fmt = "{}", _0)] diff --git a/src/types/chat_member.rs b/src/types/chat_member.rs index b8e38321..dd809633 100644 --- a/src/types/chat_member.rs +++ b/src/types/chat_member.rs @@ -7,7 +7,6 @@ use crate::types::User; /// /// [The official docs](https://core.telegram.org/bots/api#chatmember). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct ChatMember { /// Information about the user. pub user: User, diff --git a/src/types/chat_permissions.rs b/src/types/chat_permissions.rs index d6db3b6a..139f261e 100644 --- a/src/types/chat_permissions.rs +++ b/src/types/chat_permissions.rs @@ -6,7 +6,6 @@ use serde::{Deserialize, Serialize}; /// [The official docs](https://core.telegram.org/bots/api#chatpermissions). #[serde_with_macros::skip_serializing_none] #[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct ChatPermissions { /// `true`, if the user is allowed to send text messages, contacts, /// locations and venues. diff --git a/src/types/chat_photo.rs b/src/types/chat_photo.rs index 7f5ed87d..dc19be54 100644 --- a/src/types/chat_photo.rs +++ b/src/types/chat_photo.rs @@ -4,7 +4,6 @@ use serde::{Deserialize, Serialize}; /// /// [The official docs](https://core.telegram.org/bots/api#chatphoto). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct ChatPhoto { /// A file identifier of small (160x160) chat photo. This file_id can be /// used only for photo download and only for as long as the photo is diff --git a/src/types/chosen_inline_result.rs b/src/types/chosen_inline_result.rs index 18139ff0..b3aed6ee 100644 --- a/src/types/chosen_inline_result.rs +++ b/src/types/chosen_inline_result.rs @@ -10,7 +10,6 @@ use crate::types::{Location, User}; /// [result]: https://core.telegram.org/bots/api#inlinequeryresult #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct ChosenInlineResult { /// The unique identifier for the result that was chosen. pub result_id: String, diff --git a/src/types/contact.rs b/src/types/contact.rs index 68a31b38..74661c50 100644 --- a/src/types/contact.rs +++ b/src/types/contact.rs @@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize}; /// [The official docs](https://core.telegram.org/bots/api#contact). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct Contact { /// A contact's phone number. pub phone_number: String, diff --git a/src/types/dice.rs b/src/types/dice.rs index 1c221166..c06c5050 100644 --- a/src/types/dice.rs +++ b/src/types/dice.rs @@ -5,7 +5,6 @@ use crate::types::DiceEmoji; /// This object represents an animated emoji that displays a random value. #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct Dice { /// Emoji on which the dice throw animation is based. emoji: DiceEmoji, diff --git a/src/types/document.rs b/src/types/document.rs index 8a409704..aa60c2db 100644 --- a/src/types/document.rs +++ b/src/types/document.rs @@ -12,7 +12,6 @@ use crate::types::{MimeWrapper, PhotoSize}; /// [audio files]: https://core.telegram.org/bots/api#audio #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct Document { /// An identifier for this file. pub file_id: String, diff --git a/src/types/encrypted_credentials.rs b/src/types/encrypted_credentials.rs index 543e251c..332273e7 100644 --- a/src/types/encrypted_credentials.rs +++ b/src/types/encrypted_credentials.rs @@ -13,7 +13,6 @@ use serde::{Deserialize, Serialize}; /// [Telegram Passport Documentation]: https://core.telegram.org/passport#receiving-information #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct EncryptedCredentials { /// Base64-encoded encrypted JSON-serialized data with unique user's /// payload, data hashes and secrets required for diff --git a/src/types/encrypted_passport_element.rs b/src/types/encrypted_passport_element.rs index 72c0884a..98ed43a2 100644 --- a/src/types/encrypted_passport_element.rs +++ b/src/types/encrypted_passport_element.rs @@ -7,7 +7,6 @@ use super::PassportFile; /// /// [The official docs](https://core.telegram.org/bots/api#encryptedpassportelement). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct EncryptedPassportElement { /// Base64-encoded element hash for using in /// [`PassportElementErrorKind::Unspecified`]. @@ -45,7 +44,6 @@ impl EncryptedPassportElement { #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] #[allow(clippy::large_enum_variant)] -#[non_exhaustive] pub enum EncryptedPassportElementKind { PersonalDetails(EncryptedPassportElementPersonalDetails), Passport(EncryptedPassportElementPassport), @@ -64,7 +62,6 @@ pub enum EncryptedPassportElementKind { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct EncryptedPassportElementPersonalDetails { /// Base64-encoded encrypted Telegram Passport element data provided /// by the user, available for `personal_details`, `passport`, @@ -96,7 +93,6 @@ impl EncryptedPassportElementPersonalDetails { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct EncryptedPassportElementPassport { /// Base64-encoded encrypted Telegram Passport element data provided /// by the user, available for `personal_details`, `passport`, @@ -176,7 +172,6 @@ impl EncryptedPassportElementPassport { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct EncryptedPassportElementDriverLicense { /// Base64-encoded encrypted Telegram Passport element data provided /// by the user, available for `personal_details`, `passport`, @@ -274,7 +269,6 @@ impl EncryptedPassportElementDriverLicense { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct EncryptedPassportElementIdentityCard { /// Base64-encoded encrypted Telegram Passport element data provided /// by the user, available for `personal_details`, `passport`, @@ -372,7 +366,6 @@ impl EncryptedPassportElementIdentityCard { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct EncryptedPassportElementInternalPassport { /// Base64-encoded encrypted Telegram Passport element data provided /// by the user, available for `personal_details`, `passport`, @@ -452,7 +445,6 @@ impl EncryptedPassportElementInternalPassport { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct EncryptedPassportElementAddress { /// Base64-encoded encrypted Telegram Passport element data provided /// by the user, available for `personal_details`, `passport`, @@ -484,7 +476,6 @@ impl EncryptedPassportElementAddress { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct EncryptedPassportElementUtilityBill { /// Array of encrypted files with documents provided by the user, /// available for `utility_bill`, `bank_statement`, `rental_agreement`, @@ -536,7 +527,6 @@ impl EncryptedPassportElementUtilityBill { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct EncryptedPassportElementBankStatement { /// Array of encrypted files with documents provided by the user, /// available for `utility_bill`, `bank_statement`, `rental_agreement`, @@ -588,7 +578,6 @@ impl EncryptedPassportElementBankStatement { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct EncryptedPassportElementRentalAgreement { /// Array of encrypted files with documents provided by the user, /// available for `utility_bill`, `bank_statement`, `rental_agreement`, @@ -640,7 +629,6 @@ impl EncryptedPassportElementRentalAgreement { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct EncryptedPassportElementPassportRegistration { /// Array of encrypted files with documents provided by the user, /// available for `utility_bill`, `bank_statement`, `rental_agreement`, @@ -692,7 +680,6 @@ impl EncryptedPassportElementPassportRegistration { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct EncryptedPassportElementTemporaryRegistration { /// Array of encrypted files with documents provided by the user, /// available for `utility_bill`, `bank_statement`, `rental_agreement`, @@ -744,7 +731,6 @@ impl EncryptedPassportElementTemporaryRegistration { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct EncryptedPassportElementPhoneNumber { /// User's verified phone number, available only for `phone_number` /// type. @@ -770,7 +756,6 @@ impl EncryptedPassportElementPhoneNumber { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct EncryptedPassportElementEmail { /// User's verified email address, available only for `email` type. pub email: String, diff --git a/src/types/file.rs b/src/types/file.rs index 721f61c0..43734b6d 100644 --- a/src/types/file.rs +++ b/src/types/file.rs @@ -10,7 +10,6 @@ use serde::{Deserialize, Serialize}; /// /// [`Bot::get_file`]: crate::Bot::get_file #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct File { /// Identifier for this file. pub file_id: String, diff --git a/src/types/force_reply.rs b/src/types/force_reply.rs index a6a74408..cb8ed997 100644 --- a/src/types/force_reply.rs +++ b/src/types/force_reply.rs @@ -14,7 +14,6 @@ use crate::types::True; /// [privacy mode]: https://core.telegram.org/bots#privacy-mode #[serde_with_macros::skip_serializing_none] #[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct ForceReply { /// Shows reply interface to the user, as if they manually selected the /// bot‘s message and tapped ’Reply'. diff --git a/src/types/game.rs b/src/types/game.rs index 52fe01b8..56f4ef69 100644 --- a/src/types/game.rs +++ b/src/types/game.rs @@ -10,7 +10,6 @@ use crate::types::{Animation, MessageEntity, PhotoSize}; /// [@Botfather]: https://t.me/botfather #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct Game { /// Title of the game. pub title: String, diff --git a/src/types/game_high_score.rs b/src/types/game_high_score.rs index f713b18e..4d64e515 100644 --- a/src/types/game_high_score.rs +++ b/src/types/game_high_score.rs @@ -6,7 +6,6 @@ use crate::types::user::User; /// /// [The official docs](https://core.telegram.org/bots/api#gamehighscore). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct GameHighScore { /// Position in high score table for the game. pub position: u32, diff --git a/src/types/inline_keyboard_button.rs b/src/types/inline_keyboard_button.rs index fd108692..8f65d04b 100644 --- a/src/types/inline_keyboard_button.rs +++ b/src/types/inline_keyboard_button.rs @@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize}; /// /// [The official docs](https://core.telegram.org/bots/api#inlinekeyboardbutton). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineKeyboardButton { /// Label text on the button. pub text: String, @@ -38,7 +37,6 @@ impl InlineKeyboardButton { #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -#[non_exhaustive] pub enum InlineKeyboardButtonKind { /// HTTP or tg:// url to be opened when button is pressed. Url(String), diff --git a/src/types/inline_keyboard_markup.rs b/src/types/inline_keyboard_markup.rs index 06893959..d0e56444 100644 --- a/src/types/inline_keyboard_markup.rs +++ b/src/types/inline_keyboard_markup.rs @@ -12,7 +12,6 @@ use crate::types::InlineKeyboardButton; /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Default)] -#[non_exhaustive] pub struct InlineKeyboardMarkup { /// Array of button rows, each represented by an array of /// [`InlineKeyboardButton`] objects. diff --git a/src/types/inline_query.rs b/src/types/inline_query.rs index a1c15fc8..9411822d 100644 --- a/src/types/inline_query.rs +++ b/src/types/inline_query.rs @@ -10,7 +10,6 @@ use crate::types::{Location, User}; /// [The official docs](https://core.telegram.org/bots/api#inlinequery). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQuery { /// Unique identifier for this query. pub id: String, diff --git a/src/types/inline_query_result.rs b/src/types/inline_query_result.rs index cb9976de..e295bd15 100644 --- a/src/types/inline_query_result.rs +++ b/src/types/inline_query_result.rs @@ -19,7 +19,6 @@ use crate::types::{ #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, From)] #[serde(tag = "type")] #[serde(rename_all = "snake_case")] -#[non_exhaustive] pub enum InlineQueryResult { #[serde(rename = "audio")] CachedAudio(InlineQueryResultCachedAudio), diff --git a/src/types/inline_query_result_article.rs b/src/types/inline_query_result_article.rs index 2f9bd689..a4445d73 100644 --- a/src/types/inline_query_result_article.rs +++ b/src/types/inline_query_result_article.rs @@ -7,7 +7,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent}; /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultarticle). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultArticle { /// Unique identifier for this result, 1-64 Bytes. pub id: String, diff --git a/src/types/inline_query_result_audio.rs b/src/types/inline_query_result_audio.rs index 648c1bee..9e541f22 100644 --- a/src/types/inline_query_result_audio.rs +++ b/src/types/inline_query_result_audio.rs @@ -11,7 +11,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultaudio). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultAudio { /// Unique identifier for this result, 1-64 bytes. pub id: String, diff --git a/src/types/inline_query_result_cached_audio.rs b/src/types/inline_query_result_cached_audio.rs index 5efec634..8dcfa47c 100644 --- a/src/types/inline_query_result_cached_audio.rs +++ b/src/types/inline_query_result_cached_audio.rs @@ -11,7 +11,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedaudio). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultCachedAudio { /// Unique identifier for this result, 1-64 bytes. pub id: String, diff --git a/src/types/inline_query_result_cached_document.rs b/src/types/inline_query_result_cached_document.rs index 882c5858..0f119d4d 100644 --- a/src/types/inline_query_result_cached_document.rs +++ b/src/types/inline_query_result_cached_document.rs @@ -11,7 +11,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcacheddocument). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultCachedDocument { /// Unique identifier for this result, 1-64 bytes. pub id: String, diff --git a/src/types/inline_query_result_cached_gif.rs b/src/types/inline_query_result_cached_gif.rs index 79fb44db..6712ccdf 100644 --- a/src/types/inline_query_result_cached_gif.rs +++ b/src/types/inline_query_result_cached_gif.rs @@ -11,7 +11,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedgif). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultCachedGif { /// Unique identifier for this result, 1-64 bytes. pub id: String, diff --git a/src/types/inline_query_result_cached_mpeg4_gif.rs b/src/types/inline_query_result_cached_mpeg4_gif.rs index 8f97667d..cecafb0b 100644 --- a/src/types/inline_query_result_cached_mpeg4_gif.rs +++ b/src/types/inline_query_result_cached_mpeg4_gif.rs @@ -12,7 +12,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedmpeg4gif). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultCachedMpeg4Gif { /// Unique identifier for this result, 1-64 bytes. pub id: String, diff --git a/src/types/inline_query_result_cached_photo.rs b/src/types/inline_query_result_cached_photo.rs index ac9d28e8..719ec652 100644 --- a/src/types/inline_query_result_cached_photo.rs +++ b/src/types/inline_query_result_cached_photo.rs @@ -11,7 +11,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedphoto). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultCachedPhoto { /// Unique identifier for this result, 1-64 bytes. pub id: String, diff --git a/src/types/inline_query_result_cached_sticker.rs b/src/types/inline_query_result_cached_sticker.rs index 0c4e9b88..66c4138f 100644 --- a/src/types/inline_query_result_cached_sticker.rs +++ b/src/types/inline_query_result_cached_sticker.rs @@ -11,7 +11,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent}; /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedsticker). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultCachedSticker { /// Unique identifier for this result, 1-64 bytes. pub id: String, diff --git a/src/types/inline_query_result_cached_video.rs b/src/types/inline_query_result_cached_video.rs index 645892b0..b726d0ee 100644 --- a/src/types/inline_query_result_cached_video.rs +++ b/src/types/inline_query_result_cached_video.rs @@ -11,7 +11,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultCachedVideo { /// Unique identifier for this result, 1-64 bytes. pub id: String, diff --git a/src/types/inline_query_result_cached_voice.rs b/src/types/inline_query_result_cached_voice.rs index 1f50672c..cef3fa08 100644 --- a/src/types/inline_query_result_cached_voice.rs +++ b/src/types/inline_query_result_cached_voice.rs @@ -11,7 +11,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultCachedVoice { /// Unique identifier for this result, 1-64 bytes. pub id: String, diff --git a/src/types/inline_query_result_contact.rs b/src/types/inline_query_result_contact.rs index 0b4ae0d8..cffb96f0 100644 --- a/src/types/inline_query_result_contact.rs +++ b/src/types/inline_query_result_contact.rs @@ -11,7 +11,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent}; /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultContact { /// Unique identifier for this result, 1-64 Bytes. pub id: String, diff --git a/src/types/inline_query_result_document.rs b/src/types/inline_query_result_document.rs index ea66f561..7cf0a5f2 100644 --- a/src/types/inline_query_result_document.rs +++ b/src/types/inline_query_result_document.rs @@ -12,7 +12,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent, MimeWrapper, Parse /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultdocument). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultDocument { /// Unique identifier for this result, 1-64 bytes. pub id: String, diff --git a/src/types/inline_query_result_game.rs b/src/types/inline_query_result_game.rs index c795547c..0436074b 100644 --- a/src/types/inline_query_result_game.rs +++ b/src/types/inline_query_result_game.rs @@ -9,7 +9,6 @@ use crate::types::InlineKeyboardMarkup; /// [game]: https://core.telegram.org/bots/api#games #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultGame { /// Unique identifier for this result, 1-64 bytes. pub id: String, diff --git a/src/types/inline_query_result_gif.rs b/src/types/inline_query_result_gif.rs index 71145de3..73c5f36d 100644 --- a/src/types/inline_query_result_gif.rs +++ b/src/types/inline_query_result_gif.rs @@ -11,7 +11,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultgif). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultGif { /// Unique identifier for this result, 1-64 bytes. pub id: String, diff --git a/src/types/inline_query_result_location.rs b/src/types/inline_query_result_location.rs index 1d6d9270..0a4672f3 100644 --- a/src/types/inline_query_result_location.rs +++ b/src/types/inline_query_result_location.rs @@ -11,7 +11,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent}; /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultlocation). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultLocation { /// Unique identifier for this result, 1-64 Bytes. pub id: String, diff --git a/src/types/inline_query_result_mpeg4_gif.rs b/src/types/inline_query_result_mpeg4_gif.rs index 6c01ceb7..b57e4935 100644 --- a/src/types/inline_query_result_mpeg4_gif.rs +++ b/src/types/inline_query_result_mpeg4_gif.rs @@ -12,7 +12,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultmpeg4gif). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultMpeg4Gif { /// Unique identifier for this result, 1-64 bytes. pub id: String, diff --git a/src/types/inline_query_result_photo.rs b/src/types/inline_query_result_photo.rs index e1d32dc9..66e3bdfa 100644 --- a/src/types/inline_query_result_photo.rs +++ b/src/types/inline_query_result_photo.rs @@ -11,7 +11,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultphoto). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultPhoto { /// Unique identifier for this result, 1-64 bytes. pub id: String, diff --git a/src/types/inline_query_result_venue.rs b/src/types/inline_query_result_venue.rs index c7e67443..d5c16800 100644 --- a/src/types/inline_query_result_venue.rs +++ b/src/types/inline_query_result_venue.rs @@ -11,7 +11,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent}; /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultvenue). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultVenue { /// Unique identifier for this result, 1-64 Bytes. pub id: String, diff --git a/src/types/inline_query_result_video.rs b/src/types/inline_query_result_video.rs index d51258fc..7d190473 100644 --- a/src/types/inline_query_result_video.rs +++ b/src/types/inline_query_result_video.rs @@ -12,7 +12,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent, MimeWrapper, Parse /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultvideo). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultVideo { /// Unique identifier for this result, 1-64 bytes. pub id: String, diff --git a/src/types/inline_query_result_voice.rs b/src/types/inline_query_result_voice.rs index db812b7e..9365765e 100644 --- a/src/types/inline_query_result_voice.rs +++ b/src/types/inline_query_result_voice.rs @@ -12,7 +12,6 @@ use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultvoice). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InlineQueryResultVoice { /// Unique identifier for this result, 1-64 bytes. pub id: String, diff --git a/src/types/input_file.rs b/src/types/input_file.rs index d730493a..6bd0b171 100644 --- a/src/types/input_file.rs +++ b/src/types/input_file.rs @@ -6,7 +6,6 @@ use std::{borrow::Cow, path::PathBuf}; /// /// [The official docs](https://core.telegram.org/bots/api#inputfile). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub enum InputFile { File(PathBuf), Memory { file_name: String, data: Cow<'static, [u8]> }, diff --git a/src/types/input_media.rs b/src/types/input_media.rs index 160f9af8..603a4a16 100644 --- a/src/types/input_media.rs +++ b/src/types/input_media.rs @@ -8,7 +8,6 @@ use crate::types::{InputFile, ParseMode}; #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[serde(tag = "type")] #[serde(rename_all = "snake_case")] -#[non_exhaustive] pub enum InputMedia { Photo(InputMediaPhoto), Video(InputMediaVideo), @@ -22,7 +21,6 @@ pub enum InputMedia { /// [The official docs](https://core.telegram.org/bots/api#inputmediaphoto). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InputMediaPhoto { /// File to send. pub media: InputFile, @@ -68,7 +66,6 @@ impl InputMediaPhoto { /// [The official docs](https://core.telegram.org/bots/api#inputmediavideo). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InputMediaVideo { // File to send. pub media: InputFile, @@ -168,7 +165,6 @@ impl InputMediaVideo { /// [The official docs](https://core.telegram.org/bots/api#inputmediaanimation). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InputMediaAnimation { /// File to send. pub media: InputFile, @@ -258,7 +254,6 @@ impl InputMediaAnimation { /// [The official docs](https://core.telegram.org/bots/api#inputmediaaudio). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InputMediaAudio { /// File to send. pub media: InputFile, @@ -354,7 +349,6 @@ impl InputMediaAudio { /// [The official docs](https://core.telegram.org/bots/api#inputmediadocument). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InputMediaDocument { /// File to send. pub media: InputFile, diff --git a/src/types/input_message_content.rs b/src/types/input_message_content.rs index caf57650..10d1d2f3 100644 --- a/src/types/input_message_content.rs +++ b/src/types/input_message_content.rs @@ -8,7 +8,6 @@ use crate::types::ParseMode; /// [The official docs](https://core.telegram.org/bots/api#inputmessagecontent). #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(untagged)] -#[non_exhaustive] pub enum InputMessageContent { Text(InputMessageContentText), Location(InputMessageContentLocation), @@ -19,7 +18,6 @@ pub enum InputMessageContent { /// inline query. #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InputMessageContentText { /// Text of the message to be sent, 1-4096 characters. pub message_text: String, @@ -67,7 +65,6 @@ impl InputMessageContentText { /// inline query. #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InputMessageContentLocation { /// Latitude of the location in degrees. pub latitude: f64, @@ -105,7 +102,6 @@ impl InputMessageContentLocation { /// an inline query. #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InputMessageContentVenue { /// Latitude of the venue in degrees. pub latitude: f64, @@ -191,7 +187,6 @@ impl InputMessageContentVenue { /// an inline query. #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct InputMessageContentContact { /// Contact's phone number. pub phone_number: String, diff --git a/src/types/invoice.rs b/src/types/invoice.rs index 979881e1..cb4383e6 100644 --- a/src/types/invoice.rs +++ b/src/types/invoice.rs @@ -4,7 +4,6 @@ use serde::{Deserialize, Serialize}; /// /// [The official docs](https://core.telegram.org/bots/api#invoice). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct Invoice { /// Product name. pub title: String, diff --git a/src/types/keyboard_button.rs b/src/types/keyboard_button.rs index fc37134a..b245a5a2 100644 --- a/src/types/keyboard_button.rs +++ b/src/types/keyboard_button.rs @@ -10,7 +10,6 @@ use crate::types::{KeyboardButtonPollType, True}; /// [The official docs](https://core.telegram.org/bots/api#keyboardbutton). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct KeyboardButton { /// Text of the button. If none of the optional fields are used, it will /// be sent as a message when the button is pressed. @@ -44,7 +43,6 @@ impl KeyboardButton { // Serialize + Deserialize are implemented by hand #[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[non_exhaustive] pub enum ButtonRequest { Location, Contact, @@ -54,7 +52,6 @@ pub enum ButtonRequest { /// Helper struct for (de)serializing [`ButtonRequest`](ButtonRequest) #[serde_with_macros::skip_serializing_none] #[derive(Serialize, Deserialize)] -#[non_exhaustive] struct RawRequest { /// If `true`, the user's phone number will be sent as a contact /// when the button is pressed. Available in private chats only. diff --git a/src/types/keyboard_button_poll_type.rs b/src/types/keyboard_button_poll_type.rs index 6b24d540..40289dd0 100644 --- a/src/types/keyboard_button_poll_type.rs +++ b/src/types/keyboard_button_poll_type.rs @@ -1,7 +1,6 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct KeyboardButtonPollType { poll_type: String, } diff --git a/src/types/label_price.rs b/src/types/label_price.rs index 4c6841a9..704aa5c6 100644 --- a/src/types/label_price.rs +++ b/src/types/label_price.rs @@ -4,7 +4,6 @@ use serde::{Deserialize, Serialize}; /// /// [The official docs](https://core.telegram.org/bots/api#labeledprice). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct LabeledPrice { /// Portion label. pub label: String, diff --git a/src/types/location.rs b/src/types/location.rs index e34cee1a..8c37f4fb 100644 --- a/src/types/location.rs +++ b/src/types/location.rs @@ -2,7 +2,6 @@ use serde::{Deserialize, Serialize}; /// This object represents a point on the map. #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct Location { /// Longitude as defined by sender. pub longitude: f64, diff --git a/src/types/login_url.rs b/src/types/login_url.rs index 5c683df0..e0e647eb 100644 --- a/src/types/login_url.rs +++ b/src/types/login_url.rs @@ -14,7 +14,6 @@ use serde::{Deserialize, Serialize}; /// [Telegram Login Widget]: https://core.telegram.org/widgets/login #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct LoginUrl { pub url: String, pub forward_text: Option, diff --git a/src/types/mask_position.rs b/src/types/mask_position.rs index 8bca0273..cd0d0828 100644 --- a/src/types/mask_position.rs +++ b/src/types/mask_position.rs @@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize}; /// /// [The official docs](https://core.telegram.org/bots/api#maskposition). #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MaskPosition { /// The part of the face relative to which the mask should be placed. One /// of `forehead`, `eyes`, `mouth`, or `chin`. diff --git a/src/types/me.rs b/src/types/me.rs index 0b46a030..a9f33848 100644 --- a/src/types/me.rs +++ b/src/types/me.rs @@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize}; /// /// [`Bot::get_me`]: crate::Bot::get_me #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct Me { #[serde(flatten)] pub user: User, diff --git a/src/types/message.rs b/src/types/message.rs index 6404ebad..fb51b0ae 100644 --- a/src/types/message.rs +++ b/src/types/message.rs @@ -13,7 +13,6 @@ use crate::types::{ /// /// [The official docs](https://core.telegram.org/bots/api#message). #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct Message { /// Unique message identifier inside this chat. #[serde(rename = "message_id")] @@ -65,7 +64,6 @@ impl Message { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(untagged)] -#[non_exhaustive] pub enum MessageKind { Common(MessageCommon), NewChatMembers(MessageNewChatMembers), @@ -87,7 +85,6 @@ pub enum MessageKind { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MessageCommon { /// Sender, empty for messages sent to channels. pub from: Option, @@ -138,7 +135,6 @@ impl MessageCommon { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MessageNewChatMembers { /// New members that were added to the group or supergroup and /// information about them (the bot itself may be one of these @@ -164,7 +160,6 @@ impl MessageNewChatMembers { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MessageLeftChatMember { /// A member was removed from the group, information about them (this /// member may be the bot itself). @@ -189,7 +184,6 @@ impl MessageLeftChatMember { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MessageNewChatTitle { /// A chat title was changed to this value. pub new_chat_title: String, @@ -213,7 +207,6 @@ impl MessageNewChatTitle { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MessageNewChatPhoto { /// A chat photo was change to this value. pub new_chat_photo: Vec, @@ -237,7 +230,6 @@ impl MessageNewChatPhoto { } #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MessageDeleteChatPhoto { /// Service message: the chat photo was deleted. pub delete_chat_photo: True, @@ -250,7 +242,6 @@ impl MessageDeleteChatPhoto { } #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MessageGroupChatCreated { /// Service message: the group has been created. pub group_chat_created: True, @@ -263,7 +254,6 @@ impl MessageGroupChatCreated { } #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MessageSupergroupChatCreated { /// Service message: the supergroup has been created. This field can‘t /// be received in a message coming through updates, because bot can’t @@ -280,7 +270,6 @@ impl MessageSupergroupChatCreated { } #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MessageChannelChatCreated { /// Service message: the channel has been created. This field can‘t be /// received in a message coming through updates, because bot can’t be @@ -297,7 +286,6 @@ impl MessageChannelChatCreated { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MessageMigrate { /// The group has been migrated to a supergroup with the specified /// identifier. This number may be greater than 32 bits and some @@ -333,7 +321,6 @@ impl MessageMigrate { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MessagePinned { /// Specified message was pinned. Note that the Message object in this /// field will not contain further `reply_to_message` fields even if it @@ -354,7 +341,6 @@ impl MessagePinned { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MessageInvoice { /// Message is an invoice for a [payment], information about the /// invoice. [More about payments Âģ]. @@ -376,7 +362,6 @@ impl MessageInvoice { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MessageSuccessfulPayment { /// Message is a service message about a successful payment, /// information about the payment. [More about payments Âģ]. @@ -397,7 +382,6 @@ impl MessageSuccessfulPayment { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MessageConnectedWebsite { /// The domain name of the website on which the user has logged in. /// [More about Telegram Login Âģ]. @@ -424,7 +408,6 @@ impl MessageConnectedWebsite { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MessagePassportData { /// Telegram Passport data. pub passport_data: PassportData, @@ -442,7 +425,6 @@ impl MessagePassportData { } #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub enum ForwardedFrom { #[serde(rename = "forward_from")] User(User), @@ -452,7 +434,6 @@ pub enum ForwardedFrom { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(untagged)] -#[non_exhaustive] pub enum ForwardKind { Channel(ForwardChannel), NonChannel(ForwardNonChannel), @@ -460,7 +441,6 @@ pub enum ForwardKind { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct ForwardChannel { #[serde(rename = "forward_date")] pub date: i32, @@ -505,7 +485,6 @@ impl ForwardChannel { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct ForwardNonChannel { #[serde(rename = "forward_date")] pub date: i32, @@ -531,7 +510,6 @@ impl ForwardNonChannel { } #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct ForwardOrigin { pub reply_to_message: Option>, } @@ -549,7 +527,6 @@ impl ForwardOrigin { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(untagged)] -#[non_exhaustive] pub enum MediaKind { Animation(MediaAnimation), Audio(MediaAudio), @@ -568,7 +545,6 @@ pub enum MediaKind { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MediaAnimation { /// Message is an animation, information about the animation. For /// backward compatibility, when this field is set, the document field @@ -621,7 +597,6 @@ impl MediaAnimation { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MediaAudio { /// Message is an audio file, information about the file. pub audio: Audio, @@ -666,7 +641,6 @@ impl MediaAudio { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MediaContact { /// Message is a shared contact, information about the contact. contact: Contact, @@ -685,7 +659,6 @@ impl MediaContact { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MediaDocument { /// Message is a general file, information about the file. pub document: Document, @@ -730,7 +703,6 @@ impl MediaDocument { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MediaGame { /// Message is a game, information about the game. [More /// about games Âģ]. @@ -751,7 +723,6 @@ impl MediaGame { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MediaLocation { /// Message is a shared location, information about the location. pub location: Location, @@ -770,7 +741,6 @@ impl MediaLocation { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MediaPhoto { /// Message is a photo, available sizes of the photo. pub photo: Vec, @@ -836,7 +806,6 @@ impl MediaPhoto { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MediaPoll { /// Message is a native poll, information about the poll. pub poll: Poll, @@ -854,7 +823,6 @@ impl MediaPoll { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MediaSticker { /// Message is a sticker, information about the sticker. pub sticker: Sticker, @@ -872,7 +840,6 @@ impl MediaSticker { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MediaText { /// For text messages, the actual UTF-8 text of the message, 0-4096 /// characters. @@ -912,7 +879,6 @@ impl MediaText { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MediaVideo { /// Message is a video, information about the video. pub video: Video, @@ -974,7 +940,6 @@ impl MediaVideo { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MediaVideoNote { /// Message is a [video note], information about the video message. /// @@ -995,7 +960,6 @@ impl MediaVideoNote { #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MediaVoice { /// Message is a voice message, information about the file. pub voice: Voice, @@ -1040,7 +1004,6 @@ impl MediaVoice { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MediaVenue { /// Message is a venue, information about the venue. pub venue: Venue, @@ -1058,7 +1021,6 @@ impl MediaVenue { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MessageDice { /// Message is a dice with random value from 1 to 6. dice: Dice, diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs index c39459be..283199d3 100644 --- a/src/types/message_entity.rs +++ b/src/types/message_entity.rs @@ -8,7 +8,6 @@ use crate::types::{Message, User}; /// /// [The official docs](https://core.telegram.org/bots/api#messageentity). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct MessageEntity { #[serde(flatten)] pub kind: MessageEntityKind, @@ -44,7 +43,6 @@ impl MessageEntity { #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] #[serde(tag = "type")] -#[non_exhaustive] pub enum MessageEntityKind { Mention, Hashtag, diff --git a/src/types/order_info.rs b/src/types/order_info.rs index 7f41f7a9..5dca09d7 100644 --- a/src/types/order_info.rs +++ b/src/types/order_info.rs @@ -6,7 +6,6 @@ use crate::types::ShippingAddress; /// /// [The official docs](https://core.telegram.org/bots/api#orderinfo). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct OrderInfo { /// User's name. pub name: String, diff --git a/src/types/parse_mode.rs b/src/types/parse_mode.rs index 44a0ecff..989b2f24 100644 --- a/src/types/parse_mode.rs +++ b/src/types/parse_mode.rs @@ -126,7 +126,6 @@ use serde::{Deserialize, Serialize}; /// [`HTML`]: ParseMode::HTML /// [`Markdown`]: ParseMode::Markdown #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub enum ParseMode { MarkdownV2, HTML, diff --git a/src/types/passport_data.rs b/src/types/passport_data.rs index 3abaef93..c2abcd49 100644 --- a/src/types/passport_data.rs +++ b/src/types/passport_data.rs @@ -7,7 +7,6 @@ use super::{EncryptedCredentials, EncryptedPassportElement}; /// /// [The official docs](https://core.telegram.org/bots/api#passportdata). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PassportData { /// Array with information about documents and other Telegram Passport /// elements that was shared with the bot. diff --git a/src/types/passport_element_error.rs b/src/types/passport_element_error.rs index 521584e5..4ccb9335 100644 --- a/src/types/passport_element_error.rs +++ b/src/types/passport_element_error.rs @@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize}; /// /// [The official docs](https://core.telegram.org/bots/api#passportelementerror). #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PassportElementError { /// Error message. message: String, @@ -38,7 +37,6 @@ impl PassportElementError { #[serde(tag = "source")] #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub enum PassportElementErrorKind { #[serde(rename = "data")] DataField(PassportElementErrorDataField), @@ -75,7 +73,6 @@ pub enum PassportElementErrorKind { /// /// [The official docs](https://core.telegram.org/bots/api#passportelementerrordatafield). #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PassportElementErrorDataField { /// The section of the user's Telegram Passport which has the error. pub r#type: PassportElementErrorDataFieldType, @@ -129,7 +126,6 @@ impl PassportElementErrorDataField { /// /// [The official docs](https://core.telegram.org/bots/api#passportelementerrorfrontside). #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PassportElementErrorFrontSide { /// The section of the user's Telegram Passport which has the issue. pub r#type: PassportElementErrorFrontSideType, @@ -168,7 +164,6 @@ impl PassportElementErrorFrontSide { /// /// [The official docs](https://core.telegram.org/bots/api#passportelementerrorreverseside). #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PassportElementErrorReverseSide { /// The section of the user's Telegram Passport which has the issue. pub r#type: PassportElementErrorReverseSideType, @@ -206,7 +201,6 @@ impl PassportElementErrorReverseSide { /// /// [The official docs](https://core.telegram.org/bots/api#passportelementerrorselfie). #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PassportElementErrorSelfie { /// The section of the user's Telegram Passport which has the issue. pub r#type: PassportElementErrorSelfieType, @@ -244,7 +238,6 @@ impl PassportElementErrorSelfie { /// /// [The official docs](https://core.telegram.org/bots/api#passportelementerrorfile). #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PassportElementErrorFile { /// The section of the user's Telegram Passport which has the issue. pub r#type: PassportElementErrorFileType, @@ -282,7 +275,6 @@ impl PassportElementErrorFile { /// /// [The official docs](https://core.telegram.org/bots/api#passportelementerrorfiles). #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PassportElementErrorFiles { /// The section of the user's Telegram Passport which has the issue. pub r#type: PassportElementErrorFilesType, @@ -320,7 +312,6 @@ impl PassportElementErrorFiles { /// /// [The official docs](https://core.telegram.org/bots/api#passportelementerrortranslationfile). #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PassportElementErrorTranslationFile { /// Type of element of the user's Telegram Passport which has the /// issue. @@ -359,7 +350,6 @@ impl PassportElementErrorTranslationFile { /// /// [The official docs](https://core.telegram.org/bots/api#passportelementerrortranslationfiles). #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PassportElementErrorTranslationFiles { /// Type of element of the user's Telegram Passport which has the issue pub r#type: PassportElementErrorTranslationFilesType, @@ -396,7 +386,6 @@ impl PassportElementErrorTranslationFiles { /// /// [The official docs](https://core.telegram.org/bots/api#passportelementerrorunspecified). #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PassportElementErrorUnspecified { /// Type of element of the user's Telegram Passport which has the /// issue. @@ -430,7 +419,6 @@ impl PassportElementErrorUnspecified { #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -#[non_exhaustive] pub enum PassportElementErrorDataFieldType { PersonalDetails, Passport, @@ -442,7 +430,6 @@ pub enum PassportElementErrorDataFieldType { #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -#[non_exhaustive] pub enum PassportElementErrorFrontSideType { Passport, DriverLicense, @@ -452,7 +439,6 @@ pub enum PassportElementErrorFrontSideType { #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -#[non_exhaustive] pub enum PassportElementErrorReverseSideType { DriverLicense, IdentityCard, @@ -460,7 +446,6 @@ pub enum PassportElementErrorReverseSideType { #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -#[non_exhaustive] pub enum PassportElementErrorSelfieType { Passport, DriverLicense, @@ -470,7 +455,6 @@ pub enum PassportElementErrorSelfieType { #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -#[non_exhaustive] pub enum PassportElementErrorFileType { UtilityBill, BankStatement, @@ -481,7 +465,6 @@ pub enum PassportElementErrorFileType { #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -#[non_exhaustive] pub enum PassportElementErrorFilesType { UtilityBill, BankStatement, @@ -492,7 +475,6 @@ pub enum PassportElementErrorFilesType { #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -#[non_exhaustive] pub enum PassportElementErrorTranslationFileType { Passport, DriverLicense, @@ -507,7 +489,6 @@ pub enum PassportElementErrorTranslationFileType { #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -#[non_exhaustive] pub enum PassportElementErrorTranslationFilesType { Passport, DriverLicense, @@ -522,7 +503,6 @@ pub enum PassportElementErrorTranslationFilesType { #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -#[non_exhaustive] pub enum PassportElementErrorUnspecifiedType { DataField, FrontSide, diff --git a/src/types/passport_file.rs b/src/types/passport_file.rs index 448d0c62..f5c68741 100644 --- a/src/types/passport_file.rs +++ b/src/types/passport_file.rs @@ -7,7 +7,6 @@ use serde::{Deserialize, Serialize}; /// /// [The official docs](https://core.telegram.org/bots/api#passportfile). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PassportFile { /// Identifier for this file. pub file_id: String, diff --git a/src/types/photo_size.rs b/src/types/photo_size.rs index 64ddfac7..91e05639 100644 --- a/src/types/photo_size.rs +++ b/src/types/photo_size.rs @@ -6,7 +6,6 @@ use serde::{Deserialize, Serialize}; /// [sticker]: crate::types::Sticker #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PhotoSize { /// Identifier for this file. pub file_id: String, diff --git a/src/types/poll.rs b/src/types/poll.rs index c3646e3b..54020554 100644 --- a/src/types/poll.rs +++ b/src/types/poll.rs @@ -6,7 +6,6 @@ use serde::{Deserialize, Serialize}; /// [The official docs](https://core.telegram.org/bots/api#poll). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct Poll { /// Unique poll identifier. pub id: String, @@ -175,7 +174,6 @@ impl Poll { /// /// [The official docs](https://core.telegram.org/bots/api#polloption). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PollOption { /// Option text, 1-100 characters. pub text: String, diff --git a/src/types/poll_answer.rs b/src/types/poll_answer.rs index 6ddb0b06..ac8c2a6b 100644 --- a/src/types/poll_answer.rs +++ b/src/types/poll_answer.rs @@ -2,7 +2,6 @@ use crate::types::User; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PollAnswer { /// Unique poll identifier. pub poll_id: String, diff --git a/src/types/poll_type.rs b/src/types/poll_type.rs index 0243f7c1..61b8acd9 100644 --- a/src/types/poll_type.rs +++ b/src/types/poll_type.rs @@ -2,7 +2,6 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] -#[non_exhaustive] pub enum PollType { Quiz, Regular, diff --git a/src/types/pre_checkout_query.rs b/src/types/pre_checkout_query.rs index 87fd9707..6b03f084 100644 --- a/src/types/pre_checkout_query.rs +++ b/src/types/pre_checkout_query.rs @@ -7,7 +7,6 @@ use crate::types::{Currency, OrderInfo, User}; /// [The official docs](https://core.telegram.org/bots/api#precheckoutquery). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct PreCheckoutQuery { /// Unique query identifier. pub id: String, diff --git a/src/types/reply_keyboard_markup.rs b/src/types/reply_keyboard_markup.rs index a8d5983e..9289329e 100644 --- a/src/types/reply_keyboard_markup.rs +++ b/src/types/reply_keyboard_markup.rs @@ -11,7 +11,6 @@ use crate::types::KeyboardButton; /// [Introduction to bots]: https://core.telegram.org/bots#keyboards #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Default)] -#[non_exhaustive] pub struct ReplyKeyboardMarkup { /// Array of button rows, each represented by an Array of /// [`KeyboardButton`] objects diff --git a/src/types/reply_keyboard_remove.rs b/src/types/reply_keyboard_remove.rs index deb8351b..7a4d1643 100644 --- a/src/types/reply_keyboard_remove.rs +++ b/src/types/reply_keyboard_remove.rs @@ -14,7 +14,6 @@ use crate::types::True; /// [`ReplyKeyboardMarkup`]: crate::types::ReplyKeyboardMarkup #[serde_with_macros::skip_serializing_none] #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct ReplyKeyboardRemove { /// Requests clients to remove the custom keyboard (user will not be able /// to summon this keyboard; if you want to hide the keyboard from sight diff --git a/src/types/reply_markup.rs b/src/types/reply_markup.rs index 4bddc56c..5f0afbbe 100644 --- a/src/types/reply_markup.rs +++ b/src/types/reply_markup.rs @@ -5,7 +5,6 @@ use crate::types::{ForceReply, InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyK #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, From)] #[serde(untagged)] -#[non_exhaustive] pub enum ReplyMarkup { InlineKeyboardMarkup(InlineKeyboardMarkup), ReplyKeyboardMarkup(ReplyKeyboardMarkup), diff --git a/src/types/response_parameters.rs b/src/types/response_parameters.rs index 2f0fb43f..c69ce9e0 100644 --- a/src/types/response_parameters.rs +++ b/src/types/response_parameters.rs @@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize}; /// [The official docs](https://core.telegram.org/bots/api#responseparameters). #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -#[non_exhaustive] pub enum ResponseParameters { /// The group has been migrated to a supergroup with the specified /// identifier. This number may be greater than 32 bits and some diff --git a/src/types/send_invoice.rs b/src/types/send_invoice.rs index bc527b45..c2aedb61 100644 --- a/src/types/send_invoice.rs +++ b/src/types/send_invoice.rs @@ -5,7 +5,6 @@ use crate::types::{ChatId, InlineKeyboardMarkup, LabeledPrice}; // TODO: missing docs #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct SendInvoice { pub chat_id: ChatId, pub title: String, diff --git a/src/types/shipping_address.rs b/src/types/shipping_address.rs index 3a0adb58..95ae4825 100644 --- a/src/types/shipping_address.rs +++ b/src/types/shipping_address.rs @@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize}; /// /// [The official docs](https://core.telegram.org/bots/api#shippingaddress). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct ShippingAddress { /// ISO 3166-1 alpha-2 country code. pub country_code: CountryCode, diff --git a/src/types/shipping_option.rs b/src/types/shipping_option.rs index 983c7eef..af13f5ce 100644 --- a/src/types/shipping_option.rs +++ b/src/types/shipping_option.rs @@ -6,7 +6,6 @@ use crate::types::LabeledPrice; /// /// [The official docs](https://core.telegram.org/bots/api#shippingoption). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct ShippingOption { /// Shipping option identifier. pub id: String, diff --git a/src/types/shipping_query.rs b/src/types/shipping_query.rs index bfdf7dc8..06ff5985 100644 --- a/src/types/shipping_query.rs +++ b/src/types/shipping_query.rs @@ -6,7 +6,6 @@ use crate::types::{ShippingAddress, User}; /// /// [The official docs](https://core.telegram.org/bots/api#shippingquery). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct ShippingQuery { /// Unique query identifier. pub id: String, diff --git a/src/types/sticker.rs b/src/types/sticker.rs index d133f1cc..8851e8ef 100644 --- a/src/types/sticker.rs +++ b/src/types/sticker.rs @@ -7,7 +7,6 @@ use crate::types::{MaskPosition, PhotoSize}; /// [The official docs](https://core.telegram.org/bots/api#sticker). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct Sticker { /// Identifier for this file. pub file_id: String, diff --git a/src/types/sticker_set.rs b/src/types/sticker_set.rs index 0c617205..0ffb4762 100644 --- a/src/types/sticker_set.rs +++ b/src/types/sticker_set.rs @@ -6,7 +6,6 @@ use crate::types::{PhotoSize, Sticker}; /// /// [The official docs](https://core.telegram.org/bots/api#stickerset). #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct StickerSet { /// Sticker set name. pub name: String, diff --git a/src/types/sticker_type.rs b/src/types/sticker_type.rs index 607b32c6..1b6da91a 100644 --- a/src/types/sticker_type.rs +++ b/src/types/sticker_type.rs @@ -3,7 +3,6 @@ use serde::Serialize; use crate::types::InputFile; #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)] -#[non_exhaustive] #[serde(untagged)] pub enum StickerType { /// PNG image with the sticker, must be up to 512 kilobytes in size, diff --git a/src/types/successful_payment.rs b/src/types/successful_payment.rs index 439dbc23..4f7e43b5 100644 --- a/src/types/successful_payment.rs +++ b/src/types/successful_payment.rs @@ -7,7 +7,6 @@ use crate::types::{Currency, OrderInfo}; /// [The official docs](https://core.telegram.org/bots/api#successfulpayment). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct SuccessfulPayment { /// Three-letter ISO 4217 [currency] code. /// diff --git a/src/types/update.rs b/src/types/update.rs index 13514a39..e77656c1 100644 --- a/src/types/update.rs +++ b/src/types/update.rs @@ -13,7 +13,6 @@ use crate::types::{ /// /// [object]: https://core.telegram.org/bots/api#available-types #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct Update { /// The update‘s unique identifier. Update identifiers start from a certain /// positive number and increase sequentially. This ID becomes especially @@ -49,7 +48,6 @@ impl Update { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -#[non_exhaustive] pub enum UpdateKind { /// New incoming message of any kind — text, photo, sticker, etc. Message(Message), diff --git a/src/types/user.rs b/src/types/user.rs index d24f5caa..7db4c14c 100644 --- a/src/types/user.rs +++ b/src/types/user.rs @@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize}; /// [The official docs](https://core.telegram.org/bots/api#user). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct User { /// Unique identifier for this user or bot. pub id: i32, diff --git a/src/types/user_profile_photos.rs b/src/types/user_profile_photos.rs index 26d8a934..59935efc 100644 --- a/src/types/user_profile_photos.rs +++ b/src/types/user_profile_photos.rs @@ -6,7 +6,6 @@ use crate::types::PhotoSize; /// /// [The official docs](https://core.telegram.org/bots/api#userprofilephotos). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct UserProfilePhotos { /// Total number of profile pictures the target user has. pub total_count: u32, diff --git a/src/types/venue.rs b/src/types/venue.rs index 3a8ff38a..d8891a44 100644 --- a/src/types/venue.rs +++ b/src/types/venue.rs @@ -5,7 +5,6 @@ use crate::types::Location; /// This object represents a venue. #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct Venue { /// Venue location. pub location: Location, diff --git a/src/types/video.rs b/src/types/video.rs index 84de086a..98241c49 100644 --- a/src/types/video.rs +++ b/src/types/video.rs @@ -7,7 +7,6 @@ use crate::types::{MimeWrapper, PhotoSize}; /// [The official docs](https://core.telegram.org/bots/api#video). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct Video { /// Identifier for this file. pub file_id: String, diff --git a/src/types/video_note.rs b/src/types/video_note.rs index 467ace0e..d8c9ab3f 100644 --- a/src/types/video_note.rs +++ b/src/types/video_note.rs @@ -11,7 +11,6 @@ use crate::types::PhotoSize; /// [v4.0]: https://telegram.org/blog/video-messages-and-telescope #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct VideoNote { /// Identifier for this file. pub file_id: String, diff --git a/src/types/voice.rs b/src/types/voice.rs index bd522cbb..f60cc204 100644 --- a/src/types/voice.rs +++ b/src/types/voice.rs @@ -6,7 +6,6 @@ use serde::{Deserialize, Serialize}; /// [The official docs](https://core.telegram.org/bots/api#voice). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct Voice { /// Identifier for this file. pub file_id: String, diff --git a/src/types/webhook_info.rs b/src/types/webhook_info.rs index 782a588d..aad1cefb 100644 --- a/src/types/webhook_info.rs +++ b/src/types/webhook_info.rs @@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize}; /// [The official docs](https://core.telegram.org/bots/api#webhookinfo). #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] pub struct WebhookInfo { /// Webhook URL, may be empty if webhook is not set up. pub url: String, From 4467a4de8e3dec0a87607929c04166c43595c355 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 16 Aug 2020 21:30:33 +0300 Subject: [PATCH 086/755] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf1b58fa..83f09e66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - `RequestWithFile`, now multipart requests use `Request` +- Remove all `#[non_exhaustive]` annotations [`teloxide`]: https://github.com/teloxide/teloxide From 57867e7c6726deb3db4945cb882758a94a4dea49 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 16 Aug 2020 21:31:55 +0300 Subject: [PATCH 087/755] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf1b58fa..1e0364ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `RequestError::Io(io::Error)` to wrap I/O error those can happen while sending files to telegram - Change `StickerType`: instead of newtypes (`Png(InputFile)`) use structs (`Png { png_sticker: InputFile }`), add `StickerType::{png,tgs}` constructors +- Make all fields of all methods `pub` ### Removed From 45a55c66de1b38e2e30b4337e1ebe4c40a2c6724 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 22 Aug 2020 20:59:04 +0300 Subject: [PATCH 088/755] rename `Request` => `RequestOld` --- src/bot/download.rs | 2 +- src/requests/all/add_sticker_to_set.rs | 4 ++-- src/requests/all/answer_callback_query.rs | 4 ++-- src/requests/all/answer_inline_query.rs | 4 ++-- src/requests/all/answer_pre_checkout_query.rs | 4 ++-- src/requests/all/answer_shipping_query.rs | 4 ++-- src/requests/all/create_new_sticker_set.rs | 4 ++-- src/requests/all/delete_chat_photo.rs | 4 ++-- src/requests/all/delete_chat_sticker_set.rs | 4 ++-- src/requests/all/delete_message.rs | 4 ++-- src/requests/all/delete_sticker_from_set.rs | 4 ++-- src/requests/all/delete_webhook.rs | 4 ++-- src/requests/all/edit_inline_message_caption.rs | 4 ++-- src/requests/all/edit_inline_message_live_location.rs | 4 ++-- src/requests/all/edit_inline_message_media.rs | 4 ++-- src/requests/all/edit_inline_message_reply_markup.rs | 4 ++-- src/requests/all/edit_inline_message_text.rs | 4 ++-- src/requests/all/edit_message_caption.rs | 4 ++-- src/requests/all/edit_message_live_location.rs | 4 ++-- src/requests/all/edit_message_media.rs | 4 ++-- src/requests/all/edit_message_reply_markup.rs | 4 ++-- src/requests/all/edit_message_text.rs | 4 ++-- src/requests/all/export_chat_invite_link.rs | 4 ++-- src/requests/all/forward_message.rs | 4 ++-- src/requests/all/get_chat.rs | 4 ++-- src/requests/all/get_chat_administrators.rs | 4 ++-- src/requests/all/get_chat_member.rs | 4 ++-- src/requests/all/get_chat_members_count.rs | 4 ++-- src/requests/all/get_file.rs | 4 ++-- src/requests/all/get_game_high_scores.rs | 4 ++-- src/requests/all/get_me.rs | 4 ++-- src/requests/all/get_my_commands.rs | 4 ++-- src/requests/all/get_sticker_set.rs | 4 ++-- src/requests/all/get_updates.rs | 4 ++-- src/requests/all/get_updates_non_strict.rs | 4 ++-- src/requests/all/get_user_profile_photos.rs | 4 ++-- src/requests/all/get_webhook_info.rs | 4 ++-- src/requests/all/kick_chat_member.rs | 4 ++-- src/requests/all/leave_chat.rs | 4 ++-- src/requests/all/pin_chat_message.rs | 4 ++-- src/requests/all/promote_chat_member.rs | 4 ++-- src/requests/all/restrict_chat_member.rs | 4 ++-- src/requests/all/send_animation.rs | 4 ++-- src/requests/all/send_audio.rs | 4 ++-- src/requests/all/send_chat_action.rs | 4 ++-- src/requests/all/send_contact.rs | 4 ++-- src/requests/all/send_dice.rs | 4 ++-- src/requests/all/send_document.rs | 4 ++-- src/requests/all/send_game.rs | 4 ++-- src/requests/all/send_invoice.rs | 4 ++-- src/requests/all/send_location.rs | 4 ++-- src/requests/all/send_media_group.rs | 4 ++-- src/requests/all/send_message.rs | 4 ++-- src/requests/all/send_photo.rs | 4 ++-- src/requests/all/send_poll.rs | 4 ++-- src/requests/all/send_sticker.rs | 4 ++-- src/requests/all/send_venue.rs | 4 ++-- src/requests/all/send_video.rs | 4 ++-- src/requests/all/send_video_note.rs | 4 ++-- src/requests/all/send_voice.rs | 4 ++-- src/requests/all/set_chat_administrator_custom_title.rs | 4 ++-- src/requests/all/set_chat_description.rs | 4 ++-- src/requests/all/set_chat_permissions.rs | 4 ++-- src/requests/all/set_chat_photo.rs | 4 ++-- src/requests/all/set_chat_sticker_set.rs | 4 ++-- src/requests/all/set_chat_title.rs | 4 ++-- src/requests/all/set_game_score.rs | 4 ++-- src/requests/all/set_my_commands.rs | 4 ++-- src/requests/all/set_sticker_position_in_set.rs | 4 ++-- src/requests/all/set_sticker_set_thumb.rs | 4 ++-- src/requests/all/set_webhook.rs | 4 ++-- src/requests/all/stop_inline_message_live_location.rs | 4 ++-- src/requests/all/stop_message_live_location.rs | 4 ++-- src/requests/all/stop_poll.rs | 4 ++-- src/requests/all/unban_chat_member.rs | 4 ++-- src/requests/all/unpin_chat_message.rs | 4 ++-- src/requests/all/upload_sticker_file.rs | 4 ++-- src/requests/mod.rs | 2 +- 78 files changed, 154 insertions(+), 154 deletions(-) diff --git a/src/bot/download.rs b/src/bot/download.rs index 3890d79e..241ef325 100644 --- a/src/bot/download.rs +++ b/src/bot/download.rs @@ -20,7 +20,7 @@ impl Bot { /// use teloxide_core::types::File as TgFile; /// use tokio::fs::File; /// # use teloxide_core::RequestError; - /// use teloxide_core::{requests::Request, Bot}; + /// use teloxide_core::{requests::RequestOld, Bot}; /// /// # async fn run() -> Result<(), Box> { /// let bot = Bot::new("TOKEN"); diff --git a/src/requests/all/add_sticker_to_set.rs b/src/requests/all/add_sticker_to_set.rs index 3408f36b..a438b355 100644 --- a/src/requests/all/add_sticker_to_set.rs +++ b/src/requests/all/add_sticker_to_set.rs @@ -7,7 +7,7 @@ use crate::{ }; use crate::{ - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::StickerType, }; @@ -28,7 +28,7 @@ pub struct AddStickerToSet { } #[async_trait::async_trait] -impl Request for AddStickerToSet { +impl RequestOld for AddStickerToSet { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/answer_callback_query.rs b/src/requests/all/answer_callback_query.rs index c74b1e88..3ef9ecb2 100644 --- a/src/requests/all/answer_callback_query.rs +++ b/src/requests/all/answer_callback_query.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::True, Bot, }; @@ -29,7 +29,7 @@ pub struct AnswerCallbackQuery { } #[async_trait::async_trait] -impl Request for AnswerCallbackQuery { +impl RequestOld for AnswerCallbackQuery { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/answer_inline_query.rs b/src/requests/all/answer_inline_query.rs index dd1b6a38..62b9a0cd 100644 --- a/src/requests/all/answer_inline_query.rs +++ b/src/requests/all/answer_inline_query.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{InlineQueryResult, True}, Bot, }; @@ -27,7 +27,7 @@ pub struct AnswerInlineQuery { } #[async_trait::async_trait] -impl Request for AnswerInlineQuery { +impl RequestOld for AnswerInlineQuery { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/answer_pre_checkout_query.rs b/src/requests/all/answer_pre_checkout_query.rs index 2ac4df42..93182add 100644 --- a/src/requests/all/answer_pre_checkout_query.rs +++ b/src/requests/all/answer_pre_checkout_query.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::True, Bot, }; @@ -30,7 +30,7 @@ pub struct AnswerPreCheckoutQuery { } #[async_trait::async_trait] -impl Request for AnswerPreCheckoutQuery { +impl RequestOld for AnswerPreCheckoutQuery { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/answer_shipping_query.rs b/src/requests/all/answer_shipping_query.rs index be4d6662..2f90e439 100644 --- a/src/requests/all/answer_shipping_query.rs +++ b/src/requests/all/answer_shipping_query.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ShippingOption, True}, Bot, }; @@ -27,7 +27,7 @@ pub struct AnswerShippingQuery { } #[async_trait::async_trait] -impl Request for AnswerShippingQuery { +impl RequestOld for AnswerShippingQuery { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/create_new_sticker_set.rs b/src/requests/all/create_new_sticker_set.rs index d9c5e9da..b475d916 100644 --- a/src/requests/all/create_new_sticker_set.rs +++ b/src/requests/all/create_new_sticker_set.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{MaskPosition, StickerType, True}, Bot, }; @@ -27,7 +27,7 @@ pub struct CreateNewStickerSet { } #[async_trait::async_trait] -impl Request for CreateNewStickerSet { +impl RequestOld for CreateNewStickerSet { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/delete_chat_photo.rs b/src/requests/all/delete_chat_photo.rs index 01439bfa..ecea4a19 100644 --- a/src/requests/all/delete_chat_photo.rs +++ b/src/requests/all/delete_chat_photo.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, True}, Bot, }; @@ -21,7 +21,7 @@ pub struct DeleteChatPhoto { } #[async_trait::async_trait] -impl Request for DeleteChatPhoto { +impl RequestOld for DeleteChatPhoto { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/delete_chat_sticker_set.rs b/src/requests/all/delete_chat_sticker_set.rs index 9fe228c5..26955913 100644 --- a/src/requests/all/delete_chat_sticker_set.rs +++ b/src/requests/all/delete_chat_sticker_set.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, True}, Bot, }; @@ -26,7 +26,7 @@ pub struct DeleteChatStickerSet { } #[async_trait::async_trait] -impl Request for DeleteChatStickerSet { +impl RequestOld for DeleteChatStickerSet { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/delete_message.rs b/src/requests/all/delete_message.rs index 35ead60a..60260d09 100644 --- a/src/requests/all/delete_message.rs +++ b/src/requests/all/delete_message.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, True}, Bot, }; @@ -32,7 +32,7 @@ pub struct DeleteMessage { } #[async_trait::async_trait] -impl Request for DeleteMessage { +impl RequestOld for DeleteMessage { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/delete_sticker_from_set.rs b/src/requests/all/delete_sticker_from_set.rs index 59269f7a..8fd8f8bc 100644 --- a/src/requests/all/delete_sticker_from_set.rs +++ b/src/requests/all/delete_sticker_from_set.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::True, Bot, }; @@ -19,7 +19,7 @@ pub struct DeleteStickerFromSet { } #[async_trait::async_trait] -impl Request for DeleteStickerFromSet { +impl RequestOld for DeleteStickerFromSet { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/delete_webhook.rs b/src/requests/all/delete_webhook.rs index fd3bd3ba..110cbe38 100644 --- a/src/requests/all/delete_webhook.rs +++ b/src/requests/all/delete_webhook.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::True, Bot, }; @@ -21,7 +21,7 @@ pub struct DeleteWebhook { } #[async_trait::async_trait] -impl Request for DeleteWebhook { +impl RequestOld for DeleteWebhook { type Output = True; #[allow(clippy::trivially_copy_pass_by_ref)] diff --git a/src/requests/all/edit_inline_message_caption.rs b/src/requests/all/edit_inline_message_caption.rs index e777f17c..3937bb6e 100644 --- a/src/requests/all/edit_inline_message_caption.rs +++ b/src/requests/all/edit_inline_message_caption.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{InlineKeyboardMarkup, ParseMode, True}, Bot, }; @@ -26,7 +26,7 @@ pub struct EditInlineMessageCaption { } #[async_trait::async_trait] -impl Request for EditInlineMessageCaption { +impl RequestOld for EditInlineMessageCaption { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/edit_inline_message_live_location.rs b/src/requests/all/edit_inline_message_live_location.rs index 4b267427..ee90f15b 100644 --- a/src/requests/all/edit_inline_message_live_location.rs +++ b/src/requests/all/edit_inline_message_live_location.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{InlineKeyboardMarkup, True}, Bot, }; @@ -28,7 +28,7 @@ pub struct EditInlineMessageLiveLocation { } #[async_trait::async_trait] -impl Request for EditInlineMessageLiveLocation { +impl RequestOld for EditInlineMessageLiveLocation { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/edit_inline_message_media.rs b/src/requests/all/edit_inline_message_media.rs index ea0a4070..c9e9d8e5 100644 --- a/src/requests/all/edit_inline_message_media.rs +++ b/src/requests/all/edit_inline_message_media.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{InlineKeyboardMarkup, InputMedia, True}, Bot, }; @@ -30,7 +30,7 @@ pub struct EditInlineMessageMedia { } #[async_trait::async_trait] -impl Request for EditInlineMessageMedia { +impl RequestOld for EditInlineMessageMedia { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/edit_inline_message_reply_markup.rs b/src/requests/all/edit_inline_message_reply_markup.rs index cd96c686..540bdc6c 100644 --- a/src/requests/all/edit_inline_message_reply_markup.rs +++ b/src/requests/all/edit_inline_message_reply_markup.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{InlineKeyboardMarkup, True}, Bot, }; @@ -25,7 +25,7 @@ pub struct EditInlineMessageReplyMarkup { } #[async_trait::async_trait] -impl Request for EditInlineMessageReplyMarkup { +impl RequestOld for EditInlineMessageReplyMarkup { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/edit_inline_message_text.rs b/src/requests/all/edit_inline_message_text.rs index 2b266314..15daf1ab 100644 --- a/src/requests/all/edit_inline_message_text.rs +++ b/src/requests/all/edit_inline_message_text.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{InlineKeyboardMarkup, Message, ParseMode}, Bot, }; @@ -27,7 +27,7 @@ pub struct EditInlineMessageText { } #[async_trait::async_trait] -impl Request for EditInlineMessageText { +impl RequestOld for EditInlineMessageText { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/edit_message_caption.rs b/src/requests/all/edit_message_caption.rs index 9294af93..183fc3a5 100644 --- a/src/requests/all/edit_message_caption.rs +++ b/src/requests/all/edit_message_caption.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, InlineKeyboardMarkup, Message, ParseMode}, Bot, }; @@ -27,7 +27,7 @@ pub struct EditMessageCaption { } #[async_trait::async_trait] -impl Request for EditMessageCaption { +impl RequestOld for EditMessageCaption { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/edit_message_live_location.rs b/src/requests/all/edit_message_live_location.rs index c5afd295..e64ad7fc 100644 --- a/src/requests/all/edit_message_live_location.rs +++ b/src/requests/all/edit_message_live_location.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, InlineKeyboardMarkup, Message}, Bot, }; @@ -29,7 +29,7 @@ pub struct EditMessageLiveLocation { } #[async_trait::async_trait] -impl Request for EditMessageLiveLocation { +impl RequestOld for EditMessageLiveLocation { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/edit_message_media.rs b/src/requests/all/edit_message_media.rs index 826244db..64702536 100644 --- a/src/requests/all/edit_message_media.rs +++ b/src/requests/all/edit_message_media.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, InlineKeyboardMarkup, InputMedia, Message}, Bot, }; @@ -29,7 +29,7 @@ pub struct EditMessageMedia { } #[async_trait::async_trait] -impl Request for EditMessageMedia { +impl RequestOld for EditMessageMedia { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/edit_message_reply_markup.rs b/src/requests/all/edit_message_reply_markup.rs index 66aab5bf..82681857 100644 --- a/src/requests/all/edit_message_reply_markup.rs +++ b/src/requests/all/edit_message_reply_markup.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, InlineKeyboardMarkup, Message}, Bot, }; @@ -25,7 +25,7 @@ pub struct EditMessageReplyMarkup { } #[async_trait::async_trait] -impl Request for EditMessageReplyMarkup { +impl RequestOld for EditMessageReplyMarkup { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/edit_message_text.rs b/src/requests/all/edit_message_text.rs index 12251313..52a0d58b 100644 --- a/src/requests/all/edit_message_text.rs +++ b/src/requests/all/edit_message_text.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, InlineKeyboardMarkup, Message, ParseMode}, Bot, }; @@ -28,7 +28,7 @@ pub struct EditMessageText { } #[async_trait::async_trait] -impl Request for EditMessageText { +impl RequestOld for EditMessageText { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/export_chat_invite_link.rs b/src/requests/all/export_chat_invite_link.rs index 51a26437..99a270e4 100644 --- a/src/requests/all/export_chat_invite_link.rs +++ b/src/requests/all/export_chat_invite_link.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::ChatId, Bot, }; @@ -35,7 +35,7 @@ pub struct ExportChatInviteLink { } #[async_trait::async_trait] -impl Request for ExportChatInviteLink { +impl RequestOld for ExportChatInviteLink { type Output = String; /// Returns the new invite link as `String` on success. diff --git a/src/requests/all/forward_message.rs b/src/requests/all/forward_message.rs index feb9ec02..e673f0bc 100644 --- a/src/requests/all/forward_message.rs +++ b/src/requests/all/forward_message.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, Message}, Bot, }; @@ -22,7 +22,7 @@ pub struct ForwardMessage { } #[async_trait::async_trait] -impl Request for ForwardMessage { +impl RequestOld for ForwardMessage { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/get_chat.rs b/src/requests/all/get_chat.rs index 11406776..bcd41d50 100644 --- a/src/requests/all/get_chat.rs +++ b/src/requests/all/get_chat.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{Chat, ChatId}, Bot, }; @@ -21,7 +21,7 @@ pub struct GetChat { } #[async_trait::async_trait] -impl Request for GetChat { +impl RequestOld for GetChat { type Output = Chat; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/get_chat_administrators.rs b/src/requests/all/get_chat_administrators.rs index 06657ec5..1a727880 100644 --- a/src/requests/all/get_chat_administrators.rs +++ b/src/requests/all/get_chat_administrators.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, ChatMember}, Bot, }; @@ -22,7 +22,7 @@ pub struct GetChatAdministrators { } #[async_trait::async_trait] -impl Request for GetChatAdministrators { +impl RequestOld for GetChatAdministrators { type Output = Vec; /// On success, returns an array that contains information about all chat diff --git a/src/requests/all/get_chat_member.rs b/src/requests/all/get_chat_member.rs index 4ebe3f56..79d6d5f2 100644 --- a/src/requests/all/get_chat_member.rs +++ b/src/requests/all/get_chat_member.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, ChatMember}, Bot, }; @@ -20,7 +20,7 @@ pub struct GetChatMember { } #[async_trait::async_trait] -impl Request for GetChatMember { +impl RequestOld for GetChatMember { type Output = ChatMember; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/get_chat_members_count.rs b/src/requests/all/get_chat_members_count.rs index 5c78596b..c1776dcf 100644 --- a/src/requests/all/get_chat_members_count.rs +++ b/src/requests/all/get_chat_members_count.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::ChatId, Bot, }; @@ -19,7 +19,7 @@ pub struct GetChatMembersCount { } #[async_trait::async_trait] -impl Request for GetChatMembersCount { +impl RequestOld for GetChatMembersCount { type Output = i32; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/get_file.rs b/src/requests/all/get_file.rs index d2d0556c..d2bd7e7a 100644 --- a/src/requests/all/get_file.rs +++ b/src/requests/all/get_file.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::File, Bot, }; @@ -35,7 +35,7 @@ pub struct GetFile { } #[async_trait::async_trait] -impl Request for GetFile { +impl RequestOld for GetFile { type Output = File; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/get_game_high_scores.rs b/src/requests/all/get_game_high_scores.rs index 1a43d63e..216fd627 100644 --- a/src/requests/all/get_game_high_scores.rs +++ b/src/requests/all/get_game_high_scores.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{GameHighScore, TargetMessage}, Bot, }; @@ -29,7 +29,7 @@ pub struct GetGameHighScores { } #[async_trait::async_trait] -impl Request for GetGameHighScores { +impl RequestOld for GetGameHighScores { type Output = Vec; async fn send(&self) -> ResponseResult> { diff --git a/src/requests/all/get_me.rs b/src/requests/all/get_me.rs index 567b4375..6ebbae5a 100644 --- a/src/requests/all/get_me.rs +++ b/src/requests/all/get_me.rs @@ -1,6 +1,6 @@ use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::Me, Bot, }; @@ -16,7 +16,7 @@ pub struct GetMe { } #[async_trait::async_trait] -impl Request for GetMe { +impl RequestOld for GetMe { type Output = Me; /// Returns basic information about the bot. diff --git a/src/requests/all/get_my_commands.rs b/src/requests/all/get_my_commands.rs index 586bfa28..aa8ae4a1 100644 --- a/src/requests/all/get_my_commands.rs +++ b/src/requests/all/get_my_commands.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::BotCommand, Bot, }; @@ -18,7 +18,7 @@ pub struct GetMyCommands { } #[async_trait::async_trait] -impl Request for GetMyCommands { +impl RequestOld for GetMyCommands { type Output = Vec; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/get_sticker_set.rs b/src/requests/all/get_sticker_set.rs index a8a83bea..0bef0fe7 100644 --- a/src/requests/all/get_sticker_set.rs +++ b/src/requests/all/get_sticker_set.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::StickerSet, Bot, }; @@ -19,7 +19,7 @@ pub struct GetStickerSet { } #[async_trait::async_trait] -impl Request for GetStickerSet { +impl RequestOld for GetStickerSet { type Output = StickerSet; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/get_updates.rs b/src/requests/all/get_updates.rs index a9f4558d..ced014d0 100644 --- a/src/requests/all/get_updates.rs +++ b/src/requests/all/get_updates.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{AllowedUpdate, Update}, Bot, }; @@ -29,7 +29,7 @@ pub struct GetUpdates { } #[async_trait::async_trait] -impl Request for GetUpdates { +impl RequestOld for GetUpdates { type Output = Vec; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/get_updates_non_strict.rs b/src/requests/all/get_updates_non_strict.rs index 67d326c8..77b7db44 100644 --- a/src/requests/all/get_updates_non_strict.rs +++ b/src/requests/all/get_updates_non_strict.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{GetUpdates, Request, ResponseResult}, + requests::{GetUpdates, RequestOld, ResponseResult}, types::{AllowedUpdate, NonStrictVec, Update}, Bot, }; @@ -20,7 +20,7 @@ use crate::{ pub struct GetUpdatesNonStrict(pub GetUpdates); #[async_trait::async_trait] -impl Request for GetUpdatesNonStrict { +impl RequestOld for GetUpdatesNonStrict { type Output = NonStrictVec; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/get_user_profile_photos.rs b/src/requests/all/get_user_profile_photos.rs index 663a07a4..71a4e6b9 100644 --- a/src/requests/all/get_user_profile_photos.rs +++ b/src/requests/all/get_user_profile_photos.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::UserProfilePhotos, Bot, }; @@ -21,7 +21,7 @@ pub struct GetUserProfilePhotos { } #[async_trait::async_trait] -impl Request for GetUserProfilePhotos { +impl RequestOld for GetUserProfilePhotos { type Output = UserProfilePhotos; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/get_webhook_info.rs b/src/requests/all/get_webhook_info.rs index 51424471..086b7464 100644 --- a/src/requests/all/get_webhook_info.rs +++ b/src/requests/all/get_webhook_info.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::WebhookInfo, Bot, }; @@ -22,7 +22,7 @@ pub struct GetWebhookInfo { } #[async_trait::async_trait] -impl Request for GetWebhookInfo { +impl RequestOld for GetWebhookInfo { type Output = WebhookInfo; #[allow(clippy::trivially_copy_pass_by_ref)] diff --git a/src/requests/all/kick_chat_member.rs b/src/requests/all/kick_chat_member.rs index 31953949..d82309b9 100644 --- a/src/requests/all/kick_chat_member.rs +++ b/src/requests/all/kick_chat_member.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, True}, Bot, }; @@ -28,7 +28,7 @@ pub struct KickChatMember { } #[async_trait::async_trait] -impl Request for KickChatMember { +impl RequestOld for KickChatMember { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/leave_chat.rs b/src/requests/all/leave_chat.rs index d0411efb..1b7aad6a 100644 --- a/src/requests/all/leave_chat.rs +++ b/src/requests/all/leave_chat.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, True}, Bot, }; @@ -19,7 +19,7 @@ pub struct LeaveChat { } #[async_trait::async_trait] -impl Request for LeaveChat { +impl RequestOld for LeaveChat { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/pin_chat_message.rs b/src/requests/all/pin_chat_message.rs index ae1f3227..b2547611 100644 --- a/src/requests/all/pin_chat_message.rs +++ b/src/requests/all/pin_chat_message.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, True}, Bot, }; @@ -25,7 +25,7 @@ pub struct PinChatMessage { } #[async_trait::async_trait] -impl Request for PinChatMessage { +impl RequestOld for PinChatMessage { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/promote_chat_member.rs b/src/requests/all/promote_chat_member.rs index 468b3c48..24caa2dc 100644 --- a/src/requests/all/promote_chat_member.rs +++ b/src/requests/all/promote_chat_member.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, True}, Bot, }; @@ -32,7 +32,7 @@ pub struct PromoteChatMember { } #[async_trait::async_trait] -impl Request for PromoteChatMember { +impl RequestOld for PromoteChatMember { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/restrict_chat_member.rs b/src/requests/all/restrict_chat_member.rs index 6b825b91..2068330d 100644 --- a/src/requests/all/restrict_chat_member.rs +++ b/src/requests/all/restrict_chat_member.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, ChatPermissions, True}, Bot, }; @@ -26,7 +26,7 @@ pub struct RestrictChatMember { } #[async_trait::async_trait] -impl Request for RestrictChatMember { +impl RequestOld for RestrictChatMember { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/send_animation.rs b/src/requests/all/send_animation.rs index 6e5ef1cb..f8ef0b80 100644 --- a/src/requests/all/send_animation.rs +++ b/src/requests/all/send_animation.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -33,7 +33,7 @@ pub struct SendAnimation { } #[async_trait::async_trait] -impl Request for SendAnimation { +impl RequestOld for SendAnimation { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/send_audio.rs b/src/requests/all/send_audio.rs index dc61458c..fa8ff851 100644 --- a/src/requests/all/send_audio.rs +++ b/src/requests/all/send_audio.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -37,7 +37,7 @@ pub struct SendAudio { } #[async_trait::async_trait] -impl Request for SendAudio { +impl RequestOld for SendAudio { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/send_chat_action.rs b/src/requests/all/send_chat_action.rs index 5fe853b2..2301274a 100644 --- a/src/requests/all/send_chat_action.rs +++ b/src/requests/all/send_chat_action.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, True}, Bot, }; @@ -71,7 +71,7 @@ pub enum SendChatActionKind { } #[async_trait::async_trait] -impl Request for SendChatAction { +impl RequestOld for SendChatAction { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/send_contact.rs b/src/requests/all/send_contact.rs index ef3ff034..36680019 100644 --- a/src/requests/all/send_contact.rs +++ b/src/requests/all/send_contact.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, Message, ReplyMarkup}, Bot, }; @@ -26,7 +26,7 @@ pub struct SendContact { } #[async_trait::async_trait] -impl Request for SendContact { +impl RequestOld for SendContact { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/send_dice.rs b/src/requests/all/send_dice.rs index 663d7001..8a384daf 100644 --- a/src/requests/all/send_dice.rs +++ b/src/requests/all/send_dice.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, DiceEmoji, Message, ReplyMarkup}, Bot, }; @@ -25,7 +25,7 @@ pub struct SendDice { } #[async_trait::async_trait] -impl Request for SendDice { +impl RequestOld for SendDice { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/send_document.rs b/src/requests/all/send_document.rs index e865a008..e27d7796 100644 --- a/src/requests/all/send_document.rs +++ b/src/requests/all/send_document.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -29,7 +29,7 @@ pub struct SendDocument { } #[async_trait::async_trait] -impl Request for SendDocument { +impl RequestOld for SendDocument { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/send_game.rs b/src/requests/all/send_game.rs index 03a0b0e2..d02270a0 100644 --- a/src/requests/all/send_game.rs +++ b/src/requests/all/send_game.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{InlineKeyboardMarkup, Message}, Bot, }; @@ -23,7 +23,7 @@ pub struct SendGame { } #[async_trait::async_trait] -impl Request for SendGame { +impl RequestOld for SendGame { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/send_invoice.rs b/src/requests/all/send_invoice.rs index 8466cdba..f36c5bd3 100644 --- a/src/requests/all/send_invoice.rs +++ b/src/requests/all/send_invoice.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{InlineKeyboardMarkup, LabeledPrice, Message}, Bot, }; @@ -41,7 +41,7 @@ pub struct SendInvoice { } #[async_trait::async_trait] -impl Request for SendInvoice { +impl RequestOld for SendInvoice { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/send_location.rs b/src/requests/all/send_location.rs index 20201ad1..5b78d64c 100644 --- a/src/requests/all/send_location.rs +++ b/src/requests/all/send_location.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, Message, ReplyMarkup}, Bot, }; @@ -25,7 +25,7 @@ pub struct SendLocation { } #[async_trait::async_trait] -impl Request for SendLocation { +impl RequestOld for SendLocation { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/send_media_group.rs b/src/requests/all/send_media_group.rs index e7adb68a..790dcd13 100644 --- a/src/requests/all/send_media_group.rs +++ b/src/requests/all/send_media_group.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, InputMedia, Message}, Bot, }; @@ -22,7 +22,7 @@ pub struct SendMediaGroup { } #[async_trait::async_trait] -impl Request for SendMediaGroup { +impl RequestOld for SendMediaGroup { type Output = Vec; async fn send(&self) -> ResponseResult> { diff --git a/src/requests/all/send_message.rs b/src/requests/all/send_message.rs index 29525cb9..a1c9b3b0 100644 --- a/src/requests/all/send_message.rs +++ b/src/requests/all/send_message.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -25,7 +25,7 @@ pub struct SendMessage { } #[async_trait::async_trait] -impl Request for SendMessage { +impl RequestOld for SendMessage { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/send_photo.rs b/src/requests/all/send_photo.rs index 42ad8e6f..c3f80428 100644 --- a/src/requests/all/send_photo.rs +++ b/src/requests/all/send_photo.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -25,7 +25,7 @@ pub struct SendPhoto { } #[async_trait::async_trait] -impl Request for SendPhoto { +impl RequestOld for SendPhoto { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/send_poll.rs b/src/requests/all/send_poll.rs index 3dd412a1..0ae1462e 100644 --- a/src/requests/all/send_poll.rs +++ b/src/requests/all/send_poll.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, Message, ParseMode, PollType, ReplyMarkup}, Bot, }; @@ -33,7 +33,7 @@ pub struct SendPoll { } #[async_trait::async_trait] -impl Request for SendPoll { +impl RequestOld for SendPoll { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/send_sticker.rs b/src/requests/all/send_sticker.rs index dd25c9bb..4f8f076d 100644 --- a/src/requests/all/send_sticker.rs +++ b/src/requests/all/send_sticker.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, InputFile, Message, ReplyMarkup}, Bot, }; @@ -25,7 +25,7 @@ pub struct SendSticker { } #[async_trait::async_trait] -impl Request for SendSticker { +impl RequestOld for SendSticker { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/send_venue.rs b/src/requests/all/send_venue.rs index 513ece7d..30dfcc68 100644 --- a/src/requests/all/send_venue.rs +++ b/src/requests/all/send_venue.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, Message, ReplyMarkup}, Bot, }; @@ -28,7 +28,7 @@ pub struct SendVenue { } #[async_trait::async_trait] -impl Request for SendVenue { +impl RequestOld for SendVenue { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/send_video.rs b/src/requests/all/send_video.rs index 3f218f10..c6796e03 100644 --- a/src/requests/all/send_video.rs +++ b/src/requests/all/send_video.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -34,7 +34,7 @@ pub struct SendVideo { } #[async_trait::async_trait] -impl Request for SendVideo { +impl RequestOld for SendVideo { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/send_video_note.rs b/src/requests/all/send_video_note.rs index 9a0e07dd..a3cca070 100644 --- a/src/requests/all/send_video_note.rs +++ b/src/requests/all/send_video_note.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, InputFile, Message, ReplyMarkup}, Bot, }; @@ -29,7 +29,7 @@ pub struct SendVideoNote { } #[async_trait::async_trait] -impl Request for SendVideoNote { +impl RequestOld for SendVideoNote { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/send_voice.rs b/src/requests/all/send_voice.rs index 6bfc301a..c99bfd57 100644 --- a/src/requests/all/send_voice.rs +++ b/src/requests/all/send_voice.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -35,7 +35,7 @@ pub struct SendVoice { } #[async_trait::async_trait] -impl Request for SendVoice { +impl RequestOld for SendVoice { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/set_chat_administrator_custom_title.rs b/src/requests/all/set_chat_administrator_custom_title.rs index a3211811..1427cf21 100644 --- a/src/requests/all/set_chat_administrator_custom_title.rs +++ b/src/requests/all/set_chat_administrator_custom_title.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, True}, Bot, }; @@ -22,7 +22,7 @@ pub struct SetChatAdministratorCustomTitle { } #[async_trait::async_trait] -impl Request for SetChatAdministratorCustomTitle { +impl RequestOld for SetChatAdministratorCustomTitle { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/set_chat_description.rs b/src/requests/all/set_chat_description.rs index 092c495b..acfd4acc 100644 --- a/src/requests/all/set_chat_description.rs +++ b/src/requests/all/set_chat_description.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, True}, Bot, }; @@ -24,7 +24,7 @@ pub struct SetChatDescription { } #[async_trait::async_trait] -impl Request for SetChatDescription { +impl RequestOld for SetChatDescription { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/set_chat_permissions.rs b/src/requests/all/set_chat_permissions.rs index aaa1881b..3b2b55d1 100644 --- a/src/requests/all/set_chat_permissions.rs +++ b/src/requests/all/set_chat_permissions.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, ChatPermissions, True}, Bot, }; @@ -23,7 +23,7 @@ pub struct SetChatPermissions { } #[async_trait::async_trait] -impl Request for SetChatPermissions { +impl RequestOld for SetChatPermissions { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/set_chat_photo.rs b/src/requests/all/set_chat_photo.rs index dcc5febc..8ddf9365 100644 --- a/src/requests/all/set_chat_photo.rs +++ b/src/requests/all/set_chat_photo.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, InputFile, True}, Bot, }; @@ -23,7 +23,7 @@ pub struct SetChatPhoto { } #[async_trait::async_trait] -impl Request for SetChatPhoto { +impl RequestOld for SetChatPhoto { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/set_chat_sticker_set.rs b/src/requests/all/set_chat_sticker_set.rs index 010a10f5..c21782f9 100644 --- a/src/requests/all/set_chat_sticker_set.rs +++ b/src/requests/all/set_chat_sticker_set.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, True}, Bot, }; @@ -24,7 +24,7 @@ pub struct SetChatStickerSet { } #[async_trait::async_trait] -impl Request for SetChatStickerSet { +impl RequestOld for SetChatStickerSet { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/set_chat_title.rs b/src/requests/all/set_chat_title.rs index 7d6ae6d1..73d556d4 100644 --- a/src/requests/all/set_chat_title.rs +++ b/src/requests/all/set_chat_title.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, True}, Bot, }; @@ -23,7 +23,7 @@ pub struct SetChatTitle { } #[async_trait::async_trait] -impl Request for SetChatTitle { +impl RequestOld for SetChatTitle { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/set_game_score.rs b/src/requests/all/set_game_score.rs index 8e429e3a..a5a75933 100644 --- a/src/requests/all/set_game_score.rs +++ b/src/requests/all/set_game_score.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{Message, TargetMessage}, Bot, }; @@ -32,7 +32,7 @@ pub struct SetGameScore { } #[async_trait::async_trait] -impl Request for SetGameScore { +impl RequestOld for SetGameScore { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/set_my_commands.rs b/src/requests/all/set_my_commands.rs index 831f9705..a2f0785f 100644 --- a/src/requests/all/set_my_commands.rs +++ b/src/requests/all/set_my_commands.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{BotCommand, True}, Bot, }; @@ -20,7 +20,7 @@ pub struct SetMyCommands { } #[async_trait::async_trait] -impl Request for SetMyCommands { +impl RequestOld for SetMyCommands { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/set_sticker_position_in_set.rs b/src/requests/all/set_sticker_position_in_set.rs index 4ed78551..5c649adb 100644 --- a/src/requests/all/set_sticker_position_in_set.rs +++ b/src/requests/all/set_sticker_position_in_set.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::True, Bot, }; @@ -21,7 +21,7 @@ pub struct SetStickerPositionInSet { } #[async_trait::async_trait] -impl Request for SetStickerPositionInSet { +impl RequestOld for SetStickerPositionInSet { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/set_sticker_set_thumb.rs b/src/requests/all/set_sticker_set_thumb.rs index 1f84aacd..51d04041 100644 --- a/src/requests/all/set_sticker_set_thumb.rs +++ b/src/requests/all/set_sticker_set_thumb.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{InputFile, True}, Bot, }; @@ -22,7 +22,7 @@ pub struct SetStickerSetThumb { } #[async_trait::async_trait] -impl Request for SetStickerSetThumb { +impl RequestOld for SetStickerSetThumb { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/set_webhook.rs b/src/requests/all/set_webhook.rs index c2aff792..0eb38fe4 100644 --- a/src/requests/all/set_webhook.rs +++ b/src/requests/all/set_webhook.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{AllowedUpdate, InputFile, True}, Bot, }; @@ -35,7 +35,7 @@ pub struct SetWebhook { } #[async_trait::async_trait] -impl Request for SetWebhook { +impl RequestOld for SetWebhook { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/stop_inline_message_live_location.rs b/src/requests/all/stop_inline_message_live_location.rs index 40fb5604..43b80f46 100644 --- a/src/requests/all/stop_inline_message_live_location.rs +++ b/src/requests/all/stop_inline_message_live_location.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{InlineKeyboardMarkup, Message}, Bot, }; @@ -25,7 +25,7 @@ pub struct StopInlineMessageLiveLocation { } #[async_trait::async_trait] -impl Request for StopInlineMessageLiveLocation { +impl RequestOld for StopInlineMessageLiveLocation { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/stop_message_live_location.rs b/src/requests/all/stop_message_live_location.rs index 892492ea..17d43a68 100644 --- a/src/requests/all/stop_message_live_location.rs +++ b/src/requests/all/stop_message_live_location.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, InlineKeyboardMarkup, Message}, Bot, }; @@ -26,7 +26,7 @@ pub struct StopMessageLiveLocation { } #[async_trait::async_trait] -impl Request for StopMessageLiveLocation { +impl RequestOld for StopMessageLiveLocation { type Output = Message; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/stop_poll.rs b/src/requests/all/stop_poll.rs index 946b4760..981d1efa 100644 --- a/src/requests/all/stop_poll.rs +++ b/src/requests/all/stop_poll.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, InlineKeyboardMarkup, Poll}, Bot, }; @@ -21,7 +21,7 @@ pub struct StopPoll { } #[async_trait::async_trait] -impl Request for StopPoll { +impl RequestOld for StopPoll { type Output = Poll; /// On success, the stopped [`Poll`] with the final results is returned. diff --git a/src/requests/all/unban_chat_member.rs b/src/requests/all/unban_chat_member.rs index 5aee625c..5065b21d 100644 --- a/src/requests/all/unban_chat_member.rs +++ b/src/requests/all/unban_chat_member.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, True}, Bot, }; @@ -23,7 +23,7 @@ pub struct UnbanChatMember { } #[async_trait::async_trait] -impl Request for UnbanChatMember { +impl RequestOld for UnbanChatMember { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/unpin_chat_message.rs b/src/requests/all/unpin_chat_message.rs index 15e76a99..611d4f09 100644 --- a/src/requests/all/unpin_chat_message.rs +++ b/src/requests/all/unpin_chat_message.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{ChatId, True}, Bot, }; @@ -23,7 +23,7 @@ pub struct UnpinChatMessage { } #[async_trait::async_trait] -impl Request for UnpinChatMessage { +impl RequestOld for UnpinChatMessage { type Output = True; async fn send(&self) -> ResponseResult { diff --git a/src/requests/all/upload_sticker_file.rs b/src/requests/all/upload_sticker_file.rs index b09b143f..4428c525 100644 --- a/src/requests/all/upload_sticker_file.rs +++ b/src/requests/all/upload_sticker_file.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ net, - requests::{Request, ResponseResult}, + requests::{RequestOld, ResponseResult}, types::{File, InputFile}, Bot, }; @@ -24,7 +24,7 @@ pub struct UploadStickerFile { png_sticker: InputFile, } #[async_trait::async_trait] -impl Request for UploadStickerFile { +impl RequestOld for UploadStickerFile { type Output = File; async fn send(&self) -> ResponseResult { diff --git a/src/requests/mod.rs b/src/requests/mod.rs index 86536cb9..238a5fc9 100644 --- a/src/requests/mod.rs +++ b/src/requests/mod.rs @@ -10,7 +10,7 @@ pub type ResponseResult = Result; /// Designates an API request. #[async_trait::async_trait] -pub trait Request { +pub trait RequestOld { /// A data structure returned if success. type Output; From 76d47276f682ec4160a4553a46770963df325bdb Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 22 Aug 2020 21:58:22 +0300 Subject: [PATCH 089/755] requests redesign part 0 This commit introduces 3 traits mentioned in requests redesign proposal: - `Payload` - `HasPayload` - `Request` Also it adds `Output` alias to `<::Payload as Payload>::Output` --- src/lib.rs | 3 +- src/requests/has_payload.rs | 45 ++++++++++++++++++++++++ src/requests/mod.rs | 10 ++++++ src/requests/payload.rs | 20 +++++++++++ src/requests/request.rs | 69 +++++++++++++++++++++++++++++++++++++ 5 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 src/requests/has_payload.rs create mode 100644 src/requests/payload.rs create mode 100644 src/requests/request.rs diff --git a/src/lib.rs b/src/lib.rs index 8ea7ef81..4736649d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,8 @@ // ```console // $ RUSTDOCFLAGS="--cfg docsrs" cargo doc --open --all-features // ``` -#![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg))] +#![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg, doc_spotlight))] +#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))] #![forbid(unsafe_code)] //#![deny(missing_docs)] diff --git a/src/requests/has_payload.rs b/src/requests/has_payload.rs new file mode 100644 index 00000000..e5e06807 --- /dev/null +++ b/src/requests/has_payload.rs @@ -0,0 +1,45 @@ +use crate::requests::Payload; + +/// Represent types that have payload inside it. E.g.: the payload itself or a +/// `Request`. +/// +/// This trait is something between [`DerefMut`] and [`BorrowMut`] — it allows +/// only one implementation per type (the [output] is associated type, not a +/// generic), have implementations for all types `P` such `P: `[`Payload`], but +/// have no magic compiler support like [`DerefMut`] does nor does it require +/// any laws about `Eq`, `Ord` and `Hash` as [`BorrowMut`] does. +/// +/// Also [output] type is bounded by [`Payload`] trait. +/// +/// This trait is mostly used to implement payload setters (on both payloads & +/// requests), so you probably won't find yourself using it directly. +/// +/// [`DerefMut`]: std::ops::DerefMut +/// [`BorrowMut`]: std::borrow::BorrowMut +/// [`Payload`]: crate::requests::Payload +/// [output]: HasPayload::Payload +pub trait HasPayload { + /// Type of the payload contained. + type Payload: Payload; + + /// Gain mutable access to the underlying payload. + fn payload_mut(&mut self) -> &mut Self::Payload; + + /// Gain immutable access to the underlying payload. + fn payload_ref(&self) -> &Self::Payload; +} + +impl

HasPayload for P +where + P: Payload, +{ + type Payload = Self; + + fn payload_mut(&mut self) -> &mut Self::Payload { + self + } + + fn payload_ref(&self) -> &Self::Payload { + self + } +} diff --git a/src/requests/mod.rs b/src/requests/mod.rs index 238a5fc9..66551904 100644 --- a/src/requests/mod.rs +++ b/src/requests/mod.rs @@ -1,5 +1,11 @@ //! API requests. +mod has_payload; +mod payload; +mod request; + +pub use self::{has_payload::HasPayload, payload::Payload, request::Request}; + mod all; mod utils; @@ -8,6 +14,10 @@ pub use all::*; /// A type that is returned after making a request to Telegram. pub type ResponseResult = Result; +/// Output of a [`Payload`] in [`HasPayload`]. Alias to +/// `<::Payload as Payload>::Output`. +pub type Output = <::Payload as Payload>::Output; + /// Designates an API request. #[async_trait::async_trait] pub trait RequestOld { diff --git a/src/requests/payload.rs b/src/requests/payload.rs new file mode 100644 index 00000000..0e02531d --- /dev/null +++ b/src/requests/payload.rs @@ -0,0 +1,20 @@ +/// Payload of a request. +/// +/// Simply speaking structs implementing this trait represent arguments of +/// a telegram bot API method. +/// +/// This trait provides some additional information needed for sending request +/// to the telegram. +#[cfg_attr(all(docsrs, feature = "nightly"), doc(spotlight))] +pub trait Payload { + /// Return type of the telegram method. + /// + /// Note: that should not include result wrappers (e.g. it should be simply + /// `Message`, `True` or something else) + type Output; + + /// Name of the telegram method. Case insensitive, though must not include + /// underscores. (e.g.: `GetMe`, `GETME`, `getme`, `getMe` are ok, but + /// `get_me` is not ok) + const NAME: &'static str; +} diff --git a/src/requests/request.rs b/src/requests/request.rs new file mode 100644 index 00000000..9deb2cdc --- /dev/null +++ b/src/requests/request.rs @@ -0,0 +1,69 @@ +use std::future::Future; + +use crate::requests::{HasPayload, Output}; + +/// A ready-to-send telegram request. +// FIXME(waffle): Write better doc for the trait +#[cfg_attr(all(docsrs, feature = "nightly"), doc(spotlight))] +pub trait Request: HasPayload { + /* + * Could be mostly `core::future::IntoFuture` though there is no reason to + * use it before it's integrated in async/await + */ + + /// Type of error that may happen during sending the request to telegram. + type Err; + + /// Type of future returned by [`send`](Request::send) method. + type Send: Future, Self::Err>>; + + /// Type of future returned by [`send_ref`](Request::send_ref) method. + /// + /// NOTE: it intentionally forbids borrowing from self + // though anyway we couldn't allow borrowing without GATs :sob: + type SendRef: Future, Self::Err>>; + + /// Send the request. + /// + /// ## Examples + // FIXME(waffle): ignored until full request redesign lands + /// ```ignore + /// # async { + /// use teloxide_core::{methods::GetMe, requests::{Request, RequestJson}, types::User, bot::Bot}; + /// + /// let bot = Bot::new("TOKEN"); + /// let method = GetMe::new(); + /// let request = JsonRequest::new(bot, method); + /// let _: User = request.send().await.unwrap(); + /// # } + /// ``` + fn send(self) -> Self::Send; + + /// Send the request. + /// + /// This method is analogous to [`send`](Request::send), but it doesn't take + /// the ownership of `self`. This allows to send the same (or slightly + /// different) requests over and over. + /// + /// _Also_ it is expected that calling this method is better than just + /// `clone`ing the requests. (because instead of copying all the data + /// and then serializing it, this method should just serialize the data) + /// + /// ## Examples + // FIXME(waffle): ignored until full request redisign lands + /// ```ignore + /// # async { + /// use teloxide_core::prelude::*; + /// + /// let bot = Bot::new("TOKEN"); + /// # let chat_ids = vec![1, 2, 3, 4].into_iter().map(Into::into); + /// + /// let mut req = bot.send_message(0, "Hi there!"); + /// for chat_id in chat_ids { + /// req.chat_id = chat_id; + /// req.send_ref().await.unwrap(); + /// } + /// # } + /// ``` + fn send_ref(&self) -> Self::SendRef; +} From c773ce7affc9b42c61933de053cc6de5cc48234e Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 24 Aug 2020 15:50:15 +0300 Subject: [PATCH 090/755] implement `HasPayload` in terms of `As{Ref,Mut}` --- src/requests/has_payload.rs | 18 +++++++----------- src/requests/payload.rs | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/requests/has_payload.rs b/src/requests/has_payload.rs index e5e06807..ef2e3d28 100644 --- a/src/requests/has_payload.rs +++ b/src/requests/has_payload.rs @@ -18,15 +18,19 @@ use crate::requests::Payload; /// [`BorrowMut`]: std::borrow::BorrowMut /// [`Payload`]: crate::requests::Payload /// [output]: HasPayload::Payload -pub trait HasPayload { +pub trait HasPayload: AsMut<::Payload> + AsRef<::Payload> { /// Type of the payload contained. type Payload: Payload; /// Gain mutable access to the underlying payload. - fn payload_mut(&mut self) -> &mut Self::Payload; + fn payload_mut(&mut self) -> &mut Self::Payload { + self.as_mut() + } /// Gain immutable access to the underlying payload. - fn payload_ref(&self) -> &Self::Payload; + fn payload_ref(&self) -> &Self::Payload { + self.as_ref() + } } impl

HasPayload for P @@ -34,12 +38,4 @@ where P: Payload, { type Payload = Self; - - fn payload_mut(&mut self) -> &mut Self::Payload { - self - } - - fn payload_ref(&self) -> &Self::Payload { - self - } } diff --git a/src/requests/payload.rs b/src/requests/payload.rs index 0e02531d..539c9971 100644 --- a/src/requests/payload.rs +++ b/src/requests/payload.rs @@ -6,7 +6,7 @@ /// This trait provides some additional information needed for sending request /// to the telegram. #[cfg_attr(all(docsrs, feature = "nightly"), doc(spotlight))] -pub trait Payload { +pub trait Payload: AsMut + AsRef { /// Return type of the telegram method. /// /// Note: that should not include result wrappers (e.g. it should be simply From 88004607d3a1b1b5db4d88121ca53a8a423c2d1d Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 24 Aug 2020 17:08:55 +0300 Subject: [PATCH 091/755] fmt --- src/requests/has_payload.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/requests/has_payload.rs b/src/requests/has_payload.rs index ef2e3d28..783e15a0 100644 --- a/src/requests/has_payload.rs +++ b/src/requests/has_payload.rs @@ -18,7 +18,9 @@ use crate::requests::Payload; /// [`BorrowMut`]: std::borrow::BorrowMut /// [`Payload`]: crate::requests::Payload /// [output]: HasPayload::Payload -pub trait HasPayload: AsMut<::Payload> + AsRef<::Payload> { +pub trait HasPayload: + AsMut<::Payload> + AsRef<::Payload> +{ /// Type of the payload contained. type Payload: Payload; From 1f295f6f36524e98e794776dcbc17cac59e90af5 Mon Sep 17 00:00:00 2001 From: Robin Hundt <24554122+robinhundt@users.noreply.github.com> Date: Fri, 28 Aug 2020 23:17:28 +0200 Subject: [PATCH 092/755] Use FromStr in default parser --- src/fields_parse.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/fields_parse.rs b/src/fields_parse.rs index 7ed83e1e..25c42dae 100644 --- a/src/fields_parse.rs +++ b/src/fields_parse.rs @@ -67,15 +67,20 @@ pub fn impl_parse_args_named( fn create_parser<'a>( parser_type: &ParserType, - types: impl Iterator, + mut types: impl Iterator, count_args: usize, ) -> quote::__private::TokenStream { let function_to_parse = match parser_type { - ParserType::Default => match count_args { - 1 => { - quote! { (|s: String| Ok((s,))) } + ParserType::Default => match types.next() { + Some(ty) => { + quote! { (|s: String| { + let res = <#ty>::from_str(&s) + .map_err(|e|ParseError::IncorrectFormat({ let e: Box = e.into(); e }))?; + Ok((res, )) + }) + } } - _ => quote! { compile_error!("Expected 1 argument") }, + None => quote! { compile_error!("Expected 1 argument") }, }, ParserType::Split { separator } => parser_with_separator( &separator.clone().unwrap_or_else(|| " ".to_owned()), From fc7126d7271f810acd2f8625981467dc516825e1 Mon Sep 17 00:00:00 2001 From: Robin Hundt <24554122+robinhundt@users.noreply.github.com> Date: Wed, 2 Sep 2020 14:29:52 +0200 Subject: [PATCH 093/755] Check count_args in create_parser --- src/fields_parse.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/fields_parse.rs b/src/fields_parse.rs index 25c42dae..440b45e5 100644 --- a/src/fields_parse.rs +++ b/src/fields_parse.rs @@ -71,8 +71,9 @@ fn create_parser<'a>( count_args: usize, ) -> quote::__private::TokenStream { let function_to_parse = match parser_type { - ParserType::Default => match types.next() { - Some(ty) => { + ParserType::Default => match count_args { + 1 => { + let ty = types.next().expect("count_args != types.len()"); quote! { (|s: String| { let res = <#ty>::from_str(&s) .map_err(|e|ParseError::IncorrectFormat({ let e: Box = e.into(); e }))?; @@ -80,7 +81,7 @@ fn create_parser<'a>( }) } } - None => quote! { compile_error!("Expected 1 argument") }, + _ => quote! { compile_error!("Expected exactly 1 argument") }, }, ParserType::Split { separator } => parser_with_separator( &separator.clone().unwrap_or_else(|| " ".to_owned()), From 07a403c27987aad1a33ec821b7a013080d3b0f55 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 19 Sep 2020 19:00:23 +0300 Subject: [PATCH 094/755] implement default Bot's {Json,Multipart}Request --- Cargo.toml | 1 + src/bot/mod.rs | 53 ++++++++++++- src/local_macros.rs | 70 +++++++++++++++++ src/net/mod.rs | 2 +- src/net/request.rs | 49 +++++++++++- src/requests/json.rs | 106 ++++++++++++++++++++++++++ src/requests/mod.rs | 4 + src/requests/multipart.rs | 116 +++++++++++++++++++++++++++++ src/requests/request.rs | 2 +- src/serde_multipart/serializers.rs | 15 ++++ 10 files changed, 413 insertions(+), 5 deletions(-) create mode 100644 src/requests/json.rs create mode 100644 src/requests/multipart.rs diff --git a/Cargo.toml b/Cargo.toml index 72ea17a1..f97d63f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ authors = [ futures = "0.3.5" tokio = { version = "0.2.21", features = ["fs", "stream"] } tokio-util = "0.3.1" +pin-project = "0.4.23" bytes = "0.5.5" async-trait = "0.1.36" reqwest = { version = "0.10.6", features = ["json", "stream"] } diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 617ed664..5c46e992 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -1,9 +1,17 @@ -use crate::types::ParseMode; +use std::{future::Future, sync::Arc, time::Duration}; + use reqwest::{ header::{HeaderMap, CONNECTION}, Client, ClientBuilder, }; -use std::{sync::Arc, time::Duration}; +use serde::{de::DeserializeOwned, Serialize}; + +use crate::{ + net, + requests::{Payload, ResponseResult}, + serde_multipart, + types::ParseMode, +}; mod api; mod download; @@ -100,6 +108,47 @@ impl Bot { } } +impl Bot { + pub(crate) fn execute_json

( + &self, + payload: &P, + ) -> impl Future> + 'static + where + P: Payload + Serialize, + P::Output: DeserializeOwned, + { + let client = self.client.clone(); + let token = Arc::clone(&self.token); + + let params = serde_json::to_vec(payload) + // this `expect` should be ok since we don't write request those may trigger error here + .expect("serialization of request to be infallible"); + + // async move to capture client&token + async move { net::request_json2(&client, token.as_ref(), P::NAME, params).await } + } + + pub(crate) fn execute_multipart

( + &self, + payload: &P, + ) -> impl Future> + where + P: Payload + Serialize, + P::Output: DeserializeOwned, + { + let client = self.client.clone(); + let token = Arc::clone(&self.token); + + let params = serde_multipart::to_form(payload); + + // async move to capture client&token¶ms + async move { + let params = params.await?; + net::request_multipart2(&client, token.as_ref(), P::NAME, params).await + } + } +} + /// Returns a builder with safe settings. /// /// By "safe settings" I mean that a client will be able to work in long time diff --git a/src/local_macros.rs b/src/local_macros.rs index 9dfa940d..c2aed978 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -36,3 +36,73 @@ macro_rules! forward_to_unsuported_ty { )+ }; } + +#[macro_use] +macro_rules! req_future { + ( + $v2:vis def: | $( $arg:ident: $ArgTy:ty ),* $(,)? | $body:block + + $(#[$($meta:tt)*])* + $v:vis $i:ident<$T:ident> ($inner:ident) -> $Out:ty + $(where $($wh:tt)*)? + ) => { + #[pin_project::pin_project] + $v struct $i<$T> + $(where $($wh)*)? + { + #[pin] + inner: $inner::$i<$T> + } + + impl<$T> $i<$T> + $(where $($wh)*)? + { + $v2 fn new($( $arg: $ArgTy ),*) -> Self { + Self { inner: $inner::def($( $arg ),*) } + } + } + + // HACK(waffle): workaround for https://github.com/rust-lang/rust/issues/55997 + mod $inner { + #![allow(type_alias_bounds)] + + // Mostly to bring `use`s + #[allow(unused_imports)] + use super::{*, $i as _}; + + #[cfg(feature = "nightly")] + pub(crate) type $i<$T> + $(where $($wh)*)? = impl ::core::future::Future; + + #[cfg(feature = "nightly")] + pub(crate) fn def<$T>($( $arg: $ArgTy ),*) -> $i<$T> + $(where $($wh)*)? + { + $body + } + + #[cfg(not(feature = "nightly"))] + pub(crate) type $i<$T> + $(where $($wh)*)? = ::core::pin::Pin>>; + + #[cfg(not(feature = "nightly"))] + pub(crate) fn def<$T>($( $arg: $ArgTy ),*) -> $i<$T> + $(where $($wh)*)? + { + Box::pin($body) + } + } + + impl<$T> ::core::future::Future for $i<$T> + $(where $($wh)*)? + { + type Output = $Out; + + fn poll(self: ::core::pin::Pin<&mut Self>, cx: &mut ::core::task::Context<'_>) -> ::core::task::Poll { + let this = self.project(); + this.inner.poll(cx) + } + } + + }; +} diff --git a/src/net/mod.rs b/src/net/mod.rs index 3278fb28..de38dacc 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -3,7 +3,7 @@ pub use download::download_file_stream; pub use self::{ download::download_file, - request::{request_json, request_multipart}, + request::{request_json, request_json2, request_multipart, request_multipart2}, telegram_response::TelegramResponse, }; diff --git a/src/net/request.rs b/src/net/request.rs index e84c3a47..824fa03c 100644 --- a/src/net/request.rs +++ b/src/net/request.rs @@ -1,6 +1,9 @@ use std::time::Duration; -use reqwest::{Client, Response}; +use reqwest::{ + header::{HeaderValue, CONTENT_TYPE}, + Client, Response, +}; use serde::{de::DeserializeOwned, Serialize}; use crate::{ @@ -61,6 +64,50 @@ where process_response(response).await } +// FIXME(waffle): +// request_{json,mutipart} are currently used in old code, so we keep them +// for now when they will not be used anymore, we should remove them +// and rename request_{json,mutipart}2 => request_{json,mutipart} + +pub async fn request_multipart2( + client: &Client, + token: &str, + method_name: &str, + params: reqwest::multipart::Form, +) -> ResponseResult +where + T: DeserializeOwned, +{ + let response = client + .post(&super::method_url(TELEGRAM_API_URL, token, method_name)) + .multipart(params) + .send() + .await + .map_err(RequestError::NetworkError)?; + + process_response(response).await +} + +pub async fn request_json2( + client: &Client, + token: &str, + method_name: &str, + params: Vec, +) -> ResponseResult +where + T: DeserializeOwned, +{ + let response = client + .post(&super::method_url(TELEGRAM_API_URL, token, method_name)) + .header(CONTENT_TYPE, HeaderValue::from_static("application/json")) + .body(params) + .send() + .await + .map_err(RequestError::NetworkError)?; + + process_response(response).await +} + async fn process_response(response: Response) -> ResponseResult where T: DeserializeOwned, diff --git a/src/requests/json.rs b/src/requests/json.rs new file mode 100644 index 00000000..a9b37e24 --- /dev/null +++ b/src/requests/json.rs @@ -0,0 +1,106 @@ +use serde::{de::DeserializeOwned, Serialize}; + +use crate::{ + bot::Bot, + requests::{HasPayload, Payload, Request, ResponseResult}, + RequestError, +}; + +/// Ready-to-send telegram request. +/// +/// Note: payload will be sent to telegram using [`json`] +/// +/// [`json`]: https://core.telegram.org/bots/api#making-requests +#[must_use = "requests do nothing until sent"] +pub struct JsonRequest

{ + bot: Bot, + payload: P, +} + +impl

JsonRequest

{ + pub const fn new(bot: Bot, payload: P) -> Self { + Self { bot, payload } + } +} + +impl

Request for JsonRequest

+where + // FIXME(waffle): + // this is required on stable because of + // https://github.com/rust-lang/rust/issues/76882 + // when it's resolved or `type_alias_impl_trait` feature + // stabilized, we should remove 'static restriction + // + // (though critically, currently we have no + // non-'static payloads) + P: 'static, + P: Payload + Serialize, + P::Output: DeserializeOwned, +{ + type Err = RequestError; + type Send = Send

; + type SendRef = SendRef

; + + fn send(self) -> Self::Send { + Send::new(self) + } + + fn send_ref(&self) -> Self::SendRef { + SendRef::new(self) + } +} + +impl

HasPayload for JsonRequest

+where + P: Payload, +{ + type Payload = P; +} + +impl

AsRef

for JsonRequest

{ + fn as_ref(&self) -> &P { + &self.payload + } +} + +impl

AsMut

for JsonRequest

{ + fn as_mut(&mut self) -> &mut P { + &mut self.payload + } +} + +impl core::ops::Deref for JsonRequest

{ + type Target = P; + + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl core::ops::DerefMut for JsonRequest

{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut() + } +} + +req_future! { + def: |it: JsonRequest| { + it.bot.execute_json(&it.payload) + } + pub Send (inner0) -> ResponseResult + where + U: 'static, + U: Payload + Serialize, + U::Output: DeserializeOwned, +} + +req_future! { + def: |it: &JsonRequest| { + it.bot.execute_json(&it.payload) + } + pub SendRef (inner1) -> ResponseResult + where + U: 'static, + U: Payload + Serialize, + U::Output: DeserializeOwned, +} diff --git a/src/requests/mod.rs b/src/requests/mod.rs index 66551904..188bfc11 100644 --- a/src/requests/mod.rs +++ b/src/requests/mod.rs @@ -7,9 +7,13 @@ mod request; pub use self::{has_payload::HasPayload, payload::Payload, request::Request}; mod all; +mod json; +mod multipart; mod utils; pub use all::*; +pub use json::JsonRequest; +pub use multipart::MultipartRequest; /// A type that is returned after making a request to Telegram. pub type ResponseResult = Result; diff --git a/src/requests/multipart.rs b/src/requests/multipart.rs new file mode 100644 index 00000000..fffaae89 --- /dev/null +++ b/src/requests/multipart.rs @@ -0,0 +1,116 @@ +use serde::{de::DeserializeOwned, Serialize}; + +use crate::{ + bot::Bot, + requests::{HasPayload, Payload, Request, ResponseResult}, + RequestError, +}; + +/// Ready-to-send telegram request. +/// +/// Note: payload will be sent to telegram using [`multipart/form-data`] +/// +/// [`multipart/form-data`]: https://core.telegram.org/bots/api#making-requests +#[must_use = "requests do nothing until sent"] +pub struct MultipartRequest

{ + bot: Bot, + payload: P, +} + +impl

MultipartRequest

{ + pub fn new(bot: Bot, payload: P) -> Self { + Self { bot, payload } + } +} + +impl

Request for MultipartRequest

+where + // FIXME(waffle): + // this is required on stable because of + // https://github.com/rust-lang/rust/issues/76882 + // when it's resolved or `type_alias_impl_trait` feature + // stabilized, we should remove 'static restriction + // + // (though critically, currently we have no + // non-'static payloads) + P: 'static, + P: Payload + Serialize, + P::Output: DeserializeOwned, +{ + type Err = RequestError; + type Send = Send

; + type SendRef = SendRef

; + + fn send(self) -> Self::Send { + Send::new(self) + } + + fn send_ref(&self) -> Self::SendRef { + SendRef::new(self) + } +} + +impl

HasPayload for MultipartRequest

+where + P: Payload, +{ + type Payload = P; +} + +impl

AsRef

for MultipartRequest

{ + fn as_ref(&self) -> &P { + &self.payload + } +} + +impl

AsMut

for MultipartRequest

{ + fn as_mut(&mut self) -> &mut P { + &mut self.payload + } +} + +impl

core::ops::Deref for MultipartRequest

+where + P: 'static, + P: Payload + Serialize, + P::Output: DeserializeOwned, +{ + type Target = P; + + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl

core::ops::DerefMut for MultipartRequest

+where + P: 'static, + P: Payload + Serialize, + P::Output: DeserializeOwned, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut() + } +} + +req_future! { + def: |it: MultipartRequest| { + it.bot.execute_multipart(&it.payload) + } + pub Send (inner0) -> ResponseResult + where + U: 'static, + U: Payload + Serialize, + U::Output: DeserializeOwned, +} + +req_future! { + def: |it: &MultipartRequest| { + it.bot.execute_multipart(&it.payload) + } + pub SendRef (inner1) -> ResponseResult + where + U: 'static, + U: Payload + Serialize, + U::Output: DeserializeOwned, +} diff --git a/src/requests/request.rs b/src/requests/request.rs index 9deb2cdc..3ec6ba63 100644 --- a/src/requests/request.rs +++ b/src/requests/request.rs @@ -50,7 +50,7 @@ pub trait Request: HasPayload { /// and then serializing it, this method should just serialize the data) /// /// ## Examples - // FIXME(waffle): ignored until full request redisign lands + // FIXME(waffle): ignored until full request redesign lands /// ```ignore /// # async { /// use teloxide_core::prelude::*; diff --git a/src/serde_multipart/serializers.rs b/src/serde_multipart/serializers.rs index 3e9e7168..e31da1a4 100644 --- a/src/serde_multipart/serializers.rs +++ b/src/serde_multipart/serializers.rs @@ -1,6 +1,7 @@ use crate::{ serde_multipart::unserializers::{InputFileUnserializer, StringUnserializer}, types::InputFile, + RequestError, }; use futures::{ future::{ready, BoxFuture}, @@ -53,6 +54,20 @@ impl std::error::Error for Error { } } +impl From for RequestError { + fn from(err: Error) -> Self { + match err { + Error::Io(ioerr) => RequestError::Io(ioerr), + // this should be ok since we don't write request those may trigger errors and + // Error is internal. + _ => unreachable!( + "we don't create requests those fail to serialize (if you see this, open an issue \ + :|)" + ), + } + } +} + pub(crate) struct MultipartTopLvlSerializer {} impl Serializer for MultipartTopLvlSerializer { From f3e000533575e238e038d25645fb2655d26d7f2d Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 22 Sep 2020 22:48:42 +0300 Subject: [PATCH 095/755] remove AsMut/AsRef bounds because of a compiler bug --- src/requests/has_payload.rs | 22 ++++++++++++++-------- src/requests/json.rs | 18 +++++++----------- src/requests/multipart.rs | 18 +++++++----------- src/requests/payload.rs | 2 +- 4 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/requests/has_payload.rs b/src/requests/has_payload.rs index 783e15a0..facca0cc 100644 --- a/src/requests/has_payload.rs +++ b/src/requests/has_payload.rs @@ -18,21 +18,19 @@ use crate::requests::Payload; /// [`BorrowMut`]: std::borrow::BorrowMut /// [`Payload`]: crate::requests::Payload /// [output]: HasPayload::Payload -pub trait HasPayload: - AsMut<::Payload> + AsRef<::Payload> +pub trait HasPayload +// FIXME(waffle): +// we wanted to use As{Mut,Ref} here, but they doesn't work +// because of https://github.com/rust-lang/rust/issues/77010 { /// Type of the payload contained. type Payload: Payload; /// Gain mutable access to the underlying payload. - fn payload_mut(&mut self) -> &mut Self::Payload { - self.as_mut() - } + fn payload_mut(&mut self) -> &mut Self::Payload; /// Gain immutable access to the underlying payload. - fn payload_ref(&self) -> &Self::Payload { - self.as_ref() - } + fn payload_ref(&self) -> &Self::Payload; } impl

HasPayload for P @@ -40,4 +38,12 @@ where P: Payload, { type Payload = Self; + + fn payload_mut(&mut self) -> &mut Self::Payload { + self + } + + fn payload_ref(&self) -> &Self::Payload { + self + } } diff --git a/src/requests/json.rs b/src/requests/json.rs index a9b37e24..51cf906a 100644 --- a/src/requests/json.rs +++ b/src/requests/json.rs @@ -55,31 +55,27 @@ where P: Payload, { type Payload = P; -} -impl

AsRef

for JsonRequest

{ - fn as_ref(&self) -> &P { - &self.payload - } -} - -impl

AsMut

for JsonRequest

{ - fn as_mut(&mut self) -> &mut P { + fn payload_mut(&mut self) -> &mut Self::Payload { &mut self.payload } + + fn payload_ref(&self) -> &Self::Payload { + &self.payload + } } impl core::ops::Deref for JsonRequest

{ type Target = P; fn deref(&self) -> &Self::Target { - self.as_ref() + self.payload_ref() } } impl core::ops::DerefMut for JsonRequest

{ fn deref_mut(&mut self) -> &mut Self::Target { - self.as_mut() + self.payload_mut() } } diff --git a/src/requests/multipart.rs b/src/requests/multipart.rs index fffaae89..009271bd 100644 --- a/src/requests/multipart.rs +++ b/src/requests/multipart.rs @@ -55,18 +55,14 @@ where P: Payload, { type Payload = P; -} -impl

AsRef

for MultipartRequest

{ - fn as_ref(&self) -> &P { - &self.payload - } -} - -impl

AsMut

for MultipartRequest

{ - fn as_mut(&mut self) -> &mut P { + fn payload_mut(&mut self) -> &mut Self::Payload { &mut self.payload } + + fn payload_ref(&self) -> &Self::Payload { + &self.payload + } } impl

core::ops::Deref for MultipartRequest

@@ -78,7 +74,7 @@ where type Target = P; fn deref(&self) -> &Self::Target { - self.as_ref() + self.payload_ref() } } @@ -89,7 +85,7 @@ where P::Output: DeserializeOwned, { fn deref_mut(&mut self) -> &mut Self::Target { - self.as_mut() + self.payload_mut() } } diff --git a/src/requests/payload.rs b/src/requests/payload.rs index 539c9971..0e02531d 100644 --- a/src/requests/payload.rs +++ b/src/requests/payload.rs @@ -6,7 +6,7 @@ /// This trait provides some additional information needed for sending request /// to the telegram. #[cfg_attr(all(docsrs, feature = "nightly"), doc(spotlight))] -pub trait Payload: AsMut + AsRef { +pub trait Payload { /// Return type of the telegram method. /// /// Note: that should not include result wrappers (e.g. it should be simply From 2cabc5fb4e3caa58337012e3c2d8e1bdb79a27ed Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 22 Sep 2020 22:52:23 +0300 Subject: [PATCH 096/755] add Requester trait and GetMe payload --- src/bot/api.rs | 26 ++++++++++++++++++-------- src/lib.rs | 2 ++ src/payloads/get_me.rs | 29 +++++++++++++++++++++++++++++ src/payloads/mod.rs | 5 +++++ src/payloads/setters.rs | 1 + src/prelude.rs | 1 + src/requests/mod.rs | 2 ++ src/requests/requester.rs | 15 +++++++++++++++ 8 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 src/payloads/get_me.rs create mode 100644 src/payloads/mod.rs create mode 100644 src/payloads/setters.rs create mode 100644 src/prelude.rs create mode 100644 src/requests/requester.rs diff --git a/src/bot/api.rs b/src/bot/api.rs index bd7e08de..be6b21f5 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -1,4 +1,5 @@ use crate::{ + payloads, requests::{ AddStickerToSet, AnswerCallbackQuery, AnswerInlineQuery, AnswerPreCheckoutQuery, AnswerShippingQuery, CreateNewStickerSet, DeleteChatPhoto, DeleteChatStickerSet, @@ -8,14 +9,15 @@ use crate::{ EditMessageReplyMarkup, EditMessageText, ExportChatInviteLink, ForwardMessage, GetChat, GetChatAdministrators, GetChatMember, GetChatMembersCount, GetFile, GetGameHighScores, GetMe, GetMyCommands, GetStickerSet, GetUpdates, GetUpdatesNonStrict, GetUserProfilePhotos, - GetWebhookInfo, KickChatMember, LeaveChat, PinChatMessage, PromoteChatMember, - RestrictChatMember, SendAnimation, SendAudio, SendChatAction, SendChatActionKind, - SendContact, SendDice, SendDocument, SendGame, SendInvoice, SendLocation, SendMediaGroup, - SendMessage, SendPhoto, SendPoll, SendSticker, SendVenue, SendVideo, SendVideoNote, - SendVoice, SetChatAdministratorCustomTitle, SetChatDescription, SetChatPermissions, - SetChatPhoto, SetChatStickerSet, SetChatTitle, SetGameScore, SetMyCommands, - SetStickerPositionInSet, SetStickerSetThumb, SetWebhook, StopInlineMessageLiveLocation, - StopMessageLiveLocation, StopPoll, UnbanChatMember, UnpinChatMessage, UploadStickerFile, + GetWebhookInfo, JsonRequest, KickChatMember, LeaveChat, PinChatMessage, PromoteChatMember, + Requester, RestrictChatMember, SendAnimation, SendAudio, SendChatAction, + SendChatActionKind, SendContact, SendDice, SendDocument, SendGame, SendInvoice, + SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendPoll, SendSticker, SendVenue, + SendVideo, SendVideoNote, SendVoice, SetChatAdministratorCustomTitle, SetChatDescription, + SetChatPermissions, SetChatPhoto, SetChatStickerSet, SetChatTitle, SetGameScore, + SetMyCommands, SetStickerPositionInSet, SetStickerSetThumb, SetWebhook, + StopInlineMessageLiveLocation, StopMessageLiveLocation, StopPoll, UnbanChatMember, + UnpinChatMessage, UploadStickerFile, }, types::{ BotCommand, ChatId, ChatPermissions, InlineQueryResult, InputFile, InputMedia, @@ -1701,3 +1703,11 @@ impl Bot { } } } + +impl Requester for Bot { + type GetMe = JsonRequest; + + fn get_me(&self) -> JsonRequest { + Self::GetMe::new(self.clone(), payloads::GetMe::new()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 4736649d..f453e1b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,8 @@ pub use self::{ errors::{ApiErrorKind, DownloadError, KnownApiErrorKind, RequestError}, }; +pub mod payloads; +pub mod prelude; pub mod requests; pub mod types; diff --git a/src/payloads/get_me.rs b/src/payloads/get_me.rs new file mode 100644 index 00000000..cabd6f22 --- /dev/null +++ b/src/payloads/get_me.rs @@ -0,0 +1,29 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + requests::{HasPayload, Payload}, + types::User, +}; + +/// A filter method for testing your bot's auth token. Requires no parameters. +/// Returns basic information about the bot in form of a [`User`] object. +/// +/// [`User`]: crate::types::User +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default, Deserialize, Serialize)] +pub struct GetMe {} + +impl GetMe { + pub const fn new() -> Self { + GetMe {} + } +} + +impl Payload for GetMe { + type Output = User; + + const NAME: &'static str = "getMe"; +} + +pub trait GetMeSetters: HasPayload + Sized {} + +impl

GetMeSetters for P where P: HasPayload {} diff --git a/src/payloads/mod.rs b/src/payloads/mod.rs new file mode 100644 index 00000000..e01013c2 --- /dev/null +++ b/src/payloads/mod.rs @@ -0,0 +1,5 @@ +pub mod setters; + +mod get_me; + +pub use get_me::{GetMe, GetMeSetters}; diff --git a/src/payloads/setters.rs b/src/payloads/setters.rs new file mode 100644 index 00000000..937d0b50 --- /dev/null +++ b/src/payloads/setters.rs @@ -0,0 +1 @@ +pub use crate::payloads::{GetMeSetters as _}; diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 00000000..f805598b --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1 @@ +pub use crate::requests::Requester; diff --git a/src/requests/mod.rs b/src/requests/mod.rs index 188bfc11..67962379 100644 --- a/src/requests/mod.rs +++ b/src/requests/mod.rs @@ -9,11 +9,13 @@ pub use self::{has_payload::HasPayload, payload::Payload, request::Request}; mod all; mod json; mod multipart; +mod requester; mod utils; pub use all::*; pub use json::JsonRequest; pub use multipart::MultipartRequest; +pub use requester::Requester; /// A type that is returned after making a request to Telegram. pub type ResponseResult = Result; diff --git a/src/requests/requester.rs b/src/requests/requester.rs new file mode 100644 index 00000000..8390b786 --- /dev/null +++ b/src/requests/requester.rs @@ -0,0 +1,15 @@ +use crate::{payloads::GetMe, requests::Request}; + +/// The trait implemented by all bots & bot wrappers. +/// Essentially a request builder factory (?). +/// +/// _This trait is included in the crate's [`prelude`](crate::prelude)_. +#[cfg_attr(all(docsrs, feature = "nightly"), doc(spotlight))] +pub trait Requester { + type GetMe: Request; + + /// For telegram documentation of the method see [`GetMe`]. + fn get_me(&self) -> Self::GetMe; + + // FIXME(waffle): add remaining 69 methods +} From 954c410c1bf57b5b8e64e454ace12c075799d134 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 22 Sep 2020 22:54:52 +0300 Subject: [PATCH 097/755] implement `get_me` caching --- Cargo.toml | 1 + src/bot/cache_me.rs | 155 ++++++++++++++++++++++++++++++++++ src/bot/mod.rs | 3 + src/payloads/setters.rs | 2 +- src/requests/mod.rs | 2 + src/requests/requester_ext.rs | 18 ++++ 6 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 src/bot/cache_me.rs create mode 100644 src/requests/requester_ext.rs diff --git a/Cargo.toml b/Cargo.toml index f97d63f3..12200248 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ uuid = { version = "0.8.1", features = ["v4"] } # for attaching input files derive_more = "0.99.9" mime = "0.3.16" thiserror = "1.0.20" +once_cell = "1.4.0" [features] # features those require nightly compiler diff --git a/src/bot/cache_me.rs b/src/bot/cache_me.rs new file mode 100644 index 00000000..feb4954b --- /dev/null +++ b/src/bot/cache_me.rs @@ -0,0 +1,155 @@ +use std::{pin::Pin, sync::Arc}; + +use futures::{ + future, + future::{ok, Ready}, + task::{Context, Poll}, + Future, +}; +use once_cell::sync::OnceCell; + +use crate::{ + payloads::GetMe, + requests::{HasPayload, Request, Requester}, + types::User, +}; + +/// `get_me` cache. +/// +/// Bot's user is hardly ever changed, so sometimes it's reasonable to cache +/// response from `get_me` method. +pub struct CacheMe { + bot: B, + me: Arc>, +} + +impl CacheMe { + /// Creates new cache. + /// + /// Note: it's recommended to use [`RequesterExt::cache_me`] instead. + pub fn new(bot: B) -> CacheMe { + Self { bot, me: Arc::new(OnceCell::new()) } + } + + /// Allows to access inner bot + pub fn inner(&self) -> &B { + &self.bot + } + + /// Unwraps inner bot + pub fn into_inner(self) -> B { + self.bot + } + + /// Clear cache. + /// + /// Returns cached response from `get_me`, if it was cached. + /// + /// Note: internally this uses [`Arc::make_mut`] so this will **not** + /// clear cache of clones of self. + pub fn clear(&mut self) -> Option { + Arc::make_mut(&mut self.me).take() + } +} + +impl Requester for CacheMe { + type GetMe = CachedMeRequest; + + fn get_me(&self) -> Self::GetMe { + match self.me.get() { + Some(user) => CachedMeRequest(Inner::Ready(user.clone()), GetMe::new()), + None => CachedMeRequest( + Inner::Pending(self.bot.get_me(), Arc::clone(&self.me)), + GetMe::new(), + ), + } + } +} + +pub struct CachedMeRequest>(Inner, GetMe); + +enum Inner> { + Ready(User), + Pending(R, Arc>), +} + +impl> Request for CachedMeRequest { + type Err = R::Err; + type Send = Send; + type SendRef = SendRef; + + fn send(self) -> Self::Send { + let fut = match self.0 { + Inner::Ready(user) => future::Either::Left(ok(user)), + Inner::Pending(req, cell) => future::Either::Right(Init(req.send(), cell)), + }; + Send(fut) + } + + fn send_ref(&self) -> Self::SendRef { + let fut = match &self.0 { + Inner::Ready(user) => future::Either::Left(ok(user.clone())), + Inner::Pending(req, cell) => { + future::Either::Right(Init(req.send_ref(), Arc::clone(cell))) + } + }; + SendRef(fut) + } +} + +impl> HasPayload for CachedMeRequest { + type Payload = GetMe; + + fn payload_mut(&mut self) -> &mut Self::Payload { + &mut self.1 + } + + fn payload_ref(&self) -> &Self::Payload { + &self.1 + } +} + +type ReadyUser = Ready>; + +#[pin_project::pin_project] +pub struct Send>( + #[pin] future::Either, Init>, +); + +impl> Future for Send { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + this.0.poll(cx) + } +} + +#[pin_project::pin_project] +pub struct SendRef>( + #[pin] future::Either, Init>, +); + +impl> Future for SendRef { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + this.0.poll(cx) + } +} + +#[pin_project::pin_project] +struct Init(#[pin] F, Arc>); + +impl>, T: Clone, E> Future for Init { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + match this.0.poll(cx) { + Poll::Ready(Ok(ok)) => Poll::Ready(Ok(this.1.get_or_init(|| ok).clone())), + poll @ Poll::Ready(_) | poll @ Poll::Pending => poll, + } + } +} diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 5c46e992..f0e17e33 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -14,8 +14,11 @@ use crate::{ }; mod api; +mod cache_me; mod download; +pub use cache_me::CacheMe; + pub(crate) const TELOXIDE_TOKEN: &str = "TELOXIDE_TOKEN"; pub(crate) const TELOXIDE_PROXY: &str = "TELOXIDE_PROXY"; diff --git a/src/payloads/setters.rs b/src/payloads/setters.rs index 937d0b50..c7cfd823 100644 --- a/src/payloads/setters.rs +++ b/src/payloads/setters.rs @@ -1 +1 @@ -pub use crate::payloads::{GetMeSetters as _}; +pub use crate::payloads::GetMeSetters as _; diff --git a/src/requests/mod.rs b/src/requests/mod.rs index 67962379..2ba00350 100644 --- a/src/requests/mod.rs +++ b/src/requests/mod.rs @@ -10,12 +10,14 @@ mod all; mod json; mod multipart; mod requester; +mod requester_ext; mod utils; pub use all::*; pub use json::JsonRequest; pub use multipart::MultipartRequest; pub use requester::Requester; +pub use requester_ext::RequesterExt; /// A type that is returned after making a request to Telegram. pub type ResponseResult = Result; diff --git a/src/requests/requester_ext.rs b/src/requests/requester_ext.rs new file mode 100644 index 00000000..aa09d5ed --- /dev/null +++ b/src/requests/requester_ext.rs @@ -0,0 +1,18 @@ +use crate::{bot::CacheMe, requests::Requester}; + +pub trait RequesterExt: Requester { + /// Add `get_me` caching ability, see [`CacheMe`] for more. + fn cache_me(self) -> CacheMe + where + Self: Sized, + { + CacheMe::new(self) + } +} + +impl RequesterExt for T +where + T: Requester, +{ + /* use default impls */ +} From 83cc634f794402b472d0c69c22199f80f7a089fc Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 22 Sep 2020 22:56:02 +0300 Subject: [PATCH 098/755] add :std::error::Error bound on Request::Err --- src/requests/request.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/requests/request.rs b/src/requests/request.rs index 3ec6ba63..b29a6c00 100644 --- a/src/requests/request.rs +++ b/src/requests/request.rs @@ -12,7 +12,7 @@ pub trait Request: HasPayload { */ /// Type of error that may happen during sending the request to telegram. - type Err; + type Err: std::error::Error; /// Type of future returned by [`send`](Request::send) method. type Send: Future, Self::Err>>; From 0ec81cf753cd5ae31b94e0e55c4fe35b3fb9a977 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 23 Sep 2020 16:53:26 +0300 Subject: [PATCH 099/755] Implement auto sending requests --- src/bot/auto_send.rs | 164 ++++++++++++++++++++++++++++++++++ src/bot/cache_me.rs | 2 + src/bot/mod.rs | 2 + src/lib.rs | 3 +- src/requests/requester_ext.rs | 10 ++- 5 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 src/bot/auto_send.rs diff --git a/src/bot/auto_send.rs b/src/bot/auto_send.rs new file mode 100644 index 00000000..2ccc6cd5 --- /dev/null +++ b/src/bot/auto_send.rs @@ -0,0 +1,164 @@ +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use crate::requests::{HasPayload, Output, Request, Requester}; + +/// Send requests automatically. +/// +/// Requests returned by ` as `[`Requester`]`>` are [`Future`]s +/// which means that you can simply `.await` them instead of using +/// `.send().await`. +/// +/// Notes: +/// 1. This wrapper should be the most outer i.e.: `AutoSend>` +/// will automatically send requests, while `CacheMe>` - won't. +/// 2. After first call to `poll` on a request you will unable to access payload +/// nor could you use [`send_ref`](Request::send_ref) +/// +/// ## Examples +/// +/// ```rust +/// use teloxide_core::{ +/// requests::{Requester, RequesterExt}, +/// types::User, +/// Bot, +/// }; +/// +/// # async { +/// let bot = Bot::new("TOKEN").auto_send(); +/// let myself: User = bot.get_me().await?; // No .send()! +/// # Ok::<_, teloxide_core::RequestError>(()) }; +/// ``` +pub struct AutoSend { + bot: B, +} + +impl AutoSend { + /// Creates new `AutoSend`. + /// + /// Note: it's recommended to use [`RequesterExt::auto_send`] instead. + /// + /// [`RequesterExt::auto_send`]: crate::requests::RequesterExt::auto_send + pub fn new(inner: B) -> AutoSend { + Self { bot: inner } + } + + /// Allows to access inner bot + pub fn inner(&self) -> &B { + &self.bot + } + + /// Unwraps inner bot + pub fn into_inner(self) -> B { + self.bot + } +} + +impl Requester for AutoSend { + type GetMe = AutoRequest; + + fn get_me(&self) -> Self::GetMe { + AutoRequest::new(self.bot.get_me()) + } +} + +#[pin_project::pin_project] +pub struct AutoRequest(#[pin] Inner); + +impl AutoRequest { + pub fn new(inner: R) -> Self { + Self(Inner::Request(inner)) + } +} + +#[pin_project::pin_project(project = InnerProj, project_replace = InnerRepl)] +enum Inner { + /// Unsent modifiable request + Request(R), + /// Sent request + Future(#[pin] R::Send), + /// This is mostly redundant variant that is used only to take ownership + /// over `Request(R)` before creating `Future(R::Send)` in + /// `AutoRequest::poll`. + /// + /// Practically we don't create this variant anywhere else and it can be + /// ignored. + Tmp, +} + +impl Request for AutoRequest { + type Err = R::Err; + type Send = R::Send; + type SendRef = R::SendRef; + + fn send(self) -> Self::Send { + match self.0 { + Inner::Request(req) => req.send(), + Inner::Future(fut) => fut, + Inner::Tmp => tmp_unreachable(), + } + } + + fn send_ref(&self) -> Self::SendRef { + match &self.0 { + Inner::Request(req) => req.send_ref(), + Inner::Future(_) => already_polled(), + Inner::Tmp => tmp_unreachable(), + } + } +} + +impl HasPayload for AutoRequest { + type Payload = R::Payload; + + fn payload_mut(&mut self) -> &mut Self::Payload { + match &mut self.0 { + Inner::Request(req) => req.payload_mut(), + Inner::Future(_) => already_polled(), + Inner::Tmp => tmp_unreachable(), + } + } + + fn payload_ref(&self) -> &Self::Payload { + match &self.0 { + Inner::Request(req) => req.payload_ref(), + Inner::Future(_) => already_polled(), + Inner::Tmp => tmp_unreachable(), + } + } +} + +impl Future for AutoRequest { + type Output = Result, R::Err>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.as_mut().project(); + + if let InnerProj::Future(fut) = this.0.as_mut().project() { + return fut.poll(cx); + } + + let inner = this.0.as_mut().project_replace(Inner::Tmp); + let inner = match inner { + InnerRepl::Request(req) => Inner::Future(req.send()), + // Practically this is unreachable + InnerRepl::Future(_) | InnerRepl::Tmp => tmp_unreachable(), + }; + this.0.as_mut().project_replace(inner); + + self.poll(cx) + } +} + +#[inline(never)] +fn tmp_unreachable() -> ! { + unreachable!("tmp is not created outside of AutoRequest::poll") +} + +#[inline(never)] +fn already_polled() -> ! { + panic!("AutoRequest was already polled") +} diff --git a/src/bot/cache_me.rs b/src/bot/cache_me.rs index feb4954b..e4a4623d 100644 --- a/src/bot/cache_me.rs +++ b/src/bot/cache_me.rs @@ -27,6 +27,8 @@ impl CacheMe { /// Creates new cache. /// /// Note: it's recommended to use [`RequesterExt::cache_me`] instead. + /// + /// [`RequesterExt::cache_me`]: crate::requests::RequesterExt::cache_me pub fn new(bot: B) -> CacheMe { Self { bot, me: Arc::new(OnceCell::new()) } } diff --git a/src/bot/mod.rs b/src/bot/mod.rs index f0e17e33..3347c932 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -14,9 +14,11 @@ use crate::{ }; mod api; +mod auto_send; mod cache_me; mod download; +pub use auto_send::AutoSend; pub use cache_me::CacheMe; pub(crate) const TELOXIDE_TOKEN: &str = "TELOXIDE_TOKEN"; diff --git a/src/lib.rs b/src/lib.rs index f453e1b3..3c26cd26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,8 +15,9 @@ #[macro_use] mod local_macros; // internal helper macros +// FIXME(waffle): rethink modules, find a place for wrappers pub use self::{ - bot::{Bot, BotBuilder}, + bot::{AutoSend, Bot, BotBuilder, CacheMe}, errors::{ApiErrorKind, DownloadError, KnownApiErrorKind, RequestError}, }; diff --git a/src/requests/requester_ext.rs b/src/requests/requester_ext.rs index aa09d5ed..6d92a531 100644 --- a/src/requests/requester_ext.rs +++ b/src/requests/requester_ext.rs @@ -1,4 +1,4 @@ -use crate::{bot::CacheMe, requests::Requester}; +use crate::{requests::Requester, AutoSend, CacheMe}; pub trait RequesterExt: Requester { /// Add `get_me` caching ability, see [`CacheMe`] for more. @@ -8,6 +8,14 @@ pub trait RequesterExt: Requester { { CacheMe::new(self) } + + /// Send requests automatically, see [`AutoSend`] for more. + fn auto_send(self) -> AutoSend + where + Self: Sized, + { + AutoSend::new(self) + } } impl RequesterExt for T From 91d78b65b3b297c4970bc5065d1e955b1f5d377f Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 23 Sep 2020 19:05:59 +0300 Subject: [PATCH 100/755] Refactor ChatMember - Replace a bunch of `Option<_>` fields with `hatMemberKind` - Remove setters (users are not expected to create this struct) - Add getters --- src/types/chat_member.rs | 404 +++++++++++++++++++++++---------------- 1 file changed, 239 insertions(+), 165 deletions(-) diff --git a/src/types/chat_member.rs b/src/types/chat_member.rs index dd809633..746c744b 100644 --- a/src/types/chat_member.rs +++ b/src/types/chat_member.rs @@ -2,7 +2,6 @@ use serde::{Deserialize, Serialize}; use crate::types::User; -// TODO: ChatMemberKind?... /// This object contains information about one member of the chat. /// /// [The official docs](https://core.telegram.org/bots/api#chatmember). @@ -12,179 +11,264 @@ pub struct ChatMember { pub user: User, /// The member's status in the chat. - pub status: ChatMemberStatus, + #[serde(flatten)] + pub kind: ChatMemberKind, +} - /// Owner and administrators only. Custom title for this user - pub custom_title: Option, +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(tag = "status")] +pub enum ChatMemberKind { + Creator { + /// Custom title for this user. + custom_title: Option, + }, + Administrator { + /// Custom title for this user. + custom_title: Option, - /// Restricted and kicked only. Date when restrictions will be lifted for - /// this user, unix time. - pub until_date: Option, + /// `true`, if the bot is allowed to edit + /// administrator privileges of that user. + can_be_edited: bool, - /// Administrators only. `true`, if the bot is allowed to edit - /// administrator privileges of that user. - pub can_be_edited: Option, + /// `true`, if the administrator can change the chat + /// title, photo and other settings. + can_change_info: bool, - /// Administrators only. `true`, if the administrator can change the chat - /// title, photo and other settings. - pub can_change_info: Option, + /// `true`, if the administrator can post in the + /// channel, channels only. + can_post_messages: Option, - /// Administrators only. `true`, if the administrator can post in the - /// channel, channels only. - pub can_post_messages: Option, + /// `true`, if the administrator can edit messages of + /// other users and can pin messages, channels only. + can_edit_messages: Option, - /// Administrators only. `true`, if the administrator can edit messages of - /// other users and can pin messages, channels only. - pub can_edit_messages: Option, + /// `true`, if the administrator can delete messages + /// of other users. + can_delete_messages: bool, - /// Administrators only. `true`, if the administrator can delete messages - /// of other users. - pub can_delete_messages: Option, + /// `true`, if the administrator can invite new users + /// to the chat. + can_invite_users: bool, - /// Administrators only. `true`, if the administrator can invite new users - /// to the chat. - pub can_invite_users: Option, + /// `true`, if the administrator can restrict, + /// ban or unban chat members. + can_restrict_members: bool, - /// Administrators only. `true`, if the administrator can restrict, - /// ban or unban chat members. - pub can_restrict_members: Option, + /// `true`, if the administrator can pin messages, + /// supergroups only. + can_pin_messages: Option, - /// Administrators only. `true`, if the administrator can pin messages, - /// supergroups only. - pub can_pin_messages: Option, + /// `true`, if the administrator can add new + /// administrators with a subset of his own privileges or demote + /// administrators that he has promoted, directly or indirectly + /// (promoted by administrators that were appointed by the + /// user). + can_promote_members: bool, + }, + Member, + Restricted { + /// Date when restrictions will be lifted for + /// this user, unix time. + until_date: i32, - /// Administrators only. `true`, if the administrator can add new - /// administrators with a subset of his own privileges or demote - /// administrators that he has promoted, directly or indirectly (promoted - /// by administrators that were appointed by the user). - pub can_promote_members: Option, + /// Restricted only. `true`, if the user can send text messages, + /// contacts, locations and venues. + can_send_messages: bool, - /// Restricted only. `true`, if the user can send text messages, - /// contacts, locations and venues. - pub can_send_messages: Option, + /// Restricted only. `true`, if the user is allowed to send audios, + /// documents, photos, videos, video notes and voice notes. + can_send_media_messages: bool, - /// Restricted only. `true`, if the user is allowed to send audios, - /// documents, photos, videos, video notes and voice notes. - pub can_send_media_messages: Option, + /// Restricted only. `true`, if the user is allowed to send animations, + /// games, stickers and use inline bots. + can_send_other_messages: bool, - /// Restricted only. `true`, if the user is allowed to send animations, - /// games, stickers and use inline bots. - pub can_send_other_messages: Option, - - /// Restricted only. `true`, if the user is allowed to add web page - /// previews to their messages. - pub can_add_web_page_previews: Option, + /// Restricted only. `true`, if the user is allowed to add web page + /// previews to their messages. + can_add_web_page_previews: bool, + }, + Left, + Kicked { + /// Date when restrictions will be lifted for + /// this user, unix time. + until_date: i32, + }, } impl ChatMember { - pub fn new(user: User, status: ChatMemberStatus) -> Self { - Self { - user, - status, - custom_title: None, - until_date: None, - can_be_edited: None, - can_change_info: None, - can_post_messages: None, - can_edit_messages: None, - can_delete_messages: None, - can_invite_users: None, - can_restrict_members: None, - can_pin_messages: None, - can_promote_members: None, - can_send_messages: None, - can_send_media_messages: None, - can_send_other_messages: None, - can_add_web_page_previews: None, + pub fn status(&self) -> ChatMemberStatus { + match &self.kind { + ChatMemberKind::Creator { .. } => ChatMemberStatus::Creator, + ChatMemberKind::Administrator { .. } => ChatMemberStatus::Administrator, + ChatMemberKind::Member => ChatMemberStatus::Member, + ChatMemberKind::Restricted { .. } => ChatMemberStatus::Restricted, + ChatMemberKind::Left => ChatMemberStatus::Left, + ChatMemberKind::Kicked { .. } => ChatMemberStatus::Kicked, + } + } +} +impl ChatMemberKind { + pub fn custom_title(&self) -> Option<&str> { + match &self { + Self::Administrator { custom_title, .. } | Self::Creator { custom_title, .. } => { + //Some(custom_title.as_str()) + custom_title.as_deref() + } + Self::Member | Self::Restricted { .. } | Self::Left | Self::Kicked { .. } => None, } } - pub fn user(mut self, val: User) -> Self { - self.user = val; - self + pub fn until_date(&self) -> Option { + match &self { + Self::Creator { .. } | Self::Administrator { .. } | Self::Member | Self::Left => None, + Self::Restricted { until_date, .. } | Self::Kicked { until_date, .. } => { + Some(*until_date) + } + } } - pub fn status(mut self, val: ChatMemberStatus) -> Self { - self.status = val; - self + pub fn can_be_edited(&self) -> Option { + match &self { + Self::Administrator { can_be_edited, .. } => Some(*can_be_edited), + Self::Creator { .. } + | Self::Member + | Self::Restricted { .. } + | Self::Left + | Self::Kicked { .. } => None, + } } - pub fn custom_title(mut self, val: S) -> Self - where - S: Into, - { - self.custom_title = Some(val.into()); - self + pub fn can_change_info(&self) -> Option { + match &self { + Self::Administrator { can_change_info, .. } => Some(*can_change_info), + Self::Creator { .. } + | Self::Member + | Self::Restricted { .. } + | Self::Left + | Self::Kicked { .. } => None, + } } - pub fn until_date(mut self, val: i32) -> Self { - self.until_date = Some(val); - self + pub fn can_post_messages(&self) -> Option { + match &self { + Self::Administrator { can_post_messages, .. } => *can_post_messages, + Self::Creator { .. } + | Self::Member + | Self::Restricted { .. } + | Self::Left + | Self::Kicked { .. } => None, + } } - pub fn can_be_edited(mut self, val: bool) -> Self { - self.can_be_edited = Some(val); - self + pub fn can_edit_messages(&self) -> Option { + match &self { + Self::Administrator { can_edit_messages, .. } => *can_edit_messages, + Self::Creator { .. } + | Self::Member + | Self::Restricted { .. } + | Self::Left + | Self::Kicked { .. } => None, + } } - pub fn can_change_info(mut self, val: bool) -> Self { - self.can_change_info = Some(val); - self + pub fn can_delete_messages(&self) -> Option { + match &self { + Self::Administrator { can_delete_messages, .. } => Some(*can_delete_messages), + Self::Creator { .. } + | Self::Member + | Self::Restricted { .. } + | Self::Left + | Self::Kicked { .. } => None, + } } - pub fn can_post_messages(mut self, val: bool) -> Self { - self.can_post_messages = Some(val); - self + pub fn can_invite_users(&self) -> Option { + match &self { + Self::Administrator { can_invite_users, .. } => Some(*can_invite_users), + Self::Creator { .. } + | Self::Member + | Self::Restricted { .. } + | Self::Left + | Self::Kicked { .. } => None, + } } - pub fn can_edit_messages(mut self, val: bool) -> Self { - self.can_edit_messages = Some(val); - self + pub fn can_restrict_members(&self) -> Option { + match &self { + Self::Administrator { can_restrict_members, .. } => Some(*can_restrict_members), + Self::Creator { .. } + | Self::Member + | Self::Restricted { .. } + | Self::Left + | Self::Kicked { .. } => None, + } } - pub fn can_delete_messages(mut self, val: bool) -> Self { - self.can_delete_messages = Some(val); - self + pub fn can_pin_messages(&self) -> Option { + match &self { + Self::Administrator { can_pin_messages, .. } => *can_pin_messages, + Self::Creator { .. } + | Self::Member + | Self::Restricted { .. } + | Self::Left + | Self::Kicked { .. } => None, + } } - pub fn can_invite_users(mut self, val: bool) -> Self { - self.can_invite_users = Some(val); - self + pub fn can_promote_members(&self) -> Option { + match &self { + Self::Administrator { can_promote_members, .. } => Some(*can_promote_members), + Self::Creator { .. } + | Self::Member + | Self::Restricted { .. } + | Self::Left + | Self::Kicked { .. } => None, + } } - pub fn can_restrict_members(mut self, val: bool) -> Self { - self.can_restrict_members = Some(val); - self + pub fn can_send_messages(&self) -> Option { + match &self { + Self::Restricted { can_send_messages, .. } => Some(*can_send_messages), + Self::Creator { .. } + | Self::Administrator { .. } + | Self::Member + | Self::Left + | Self::Kicked { .. } => None, + } } - pub fn can_pin_messages(mut self, val: bool) -> Self { - self.can_pin_messages = Some(val); - self + pub fn can_send_media_messages(&self) -> Option { + match &self { + Self::Restricted { can_send_media_messages, .. } => Some(*can_send_media_messages), + Self::Creator { .. } + | Self::Administrator { .. } + | Self::Member + | Self::Left + | Self::Kicked { .. } => None, + } } - pub fn can_promote_members(mut self, val: bool) -> Self { - self.can_promote_members = Some(val); - self + pub fn can_send_other_messages(&self) -> Option { + match &self { + Self::Restricted { can_send_other_messages, .. } => Some(*can_send_other_messages), + Self::Creator { .. } + | Self::Administrator { .. } + | Self::Member + | Self::Left + | Self::Kicked { .. } => None, + } } - pub fn can_send_messages(mut self, val: bool) -> Self { - self.can_send_messages = Some(val); - self - } - - pub fn can_send_media_messages(mut self, val: bool) -> Self { - self.can_send_media_messages = Some(val); - self - } - - pub fn can_send_other_messages(mut self, val: bool) -> Self { - self.can_send_other_messages = Some(val); - self - } - - pub fn can_add_web_page_previews(mut self, val: bool) -> Self { - self.can_add_web_page_previews = Some(val); - self + pub fn can_add_web_page_previews(&self) -> Option { + match &self { + Self::Restricted { can_add_web_page_previews, .. } => Some(*can_add_web_page_previews), + Self::Creator { .. } + | Self::Administrator { .. } + | Self::Member + | Self::Left + | Self::Kicked { .. } => None, + } } } @@ -207,53 +291,43 @@ mod tests { fn deserialize() { let json = r#"{ "user":{ - "id":12345, + "id":1029940401, "is_bot":false, - "first_name":"firstName" + "first_name":"First", + "last_name":"Last", + "username":"fl", + "language_code":"en" }, - "status":"creator", - "until_date":123456, - "can_be_edited":true, - "can_post_messages":true, - "can_edit_messages":true, - "can_delete_messages":true, - "can_restrict_members":true, - "can_promote_members":true, + "status":"administrator", + "can_be_edited":false, "can_change_info":true, + "can_delete_messages":true, "can_invite_users":true, + "can_restrict_members":true, "can_pin_messages":true, - "is_member":true, - "can_send_messages":true, - "can_send_media_messages":true, - "can_send_polls":true, - "can_send_other_messages":true, - "can_add_web_page_previews":true + "can_promote_members":true }"#; let expected = ChatMember { user: User { - id: 12345, + id: 1029940401, is_bot: false, - first_name: "firstName".to_string(), - last_name: None, - username: None, - language_code: None, + first_name: "First".to_string(), + last_name: Some("Last".to_string()), + username: Some("fl".to_string()), + language_code: Some("en".to_string()), + }, + kind: ChatMemberKind::Administrator { + custom_title: None, + can_be_edited: false, + can_change_info: true, + can_post_messages: None, + can_edit_messages: None, + can_delete_messages: true, + can_invite_users: true, + can_restrict_members: true, + can_pin_messages: Some(true), + can_promote_members: true, }, - status: ChatMemberStatus::Creator, - custom_title: None, - until_date: Some(123_456), - can_be_edited: Some(true), - can_change_info: Some(true), - can_post_messages: Some(true), - can_edit_messages: Some(true), - can_delete_messages: Some(true), - can_invite_users: Some(true), - can_restrict_members: Some(true), - can_pin_messages: Some(true), - can_promote_members: Some(true), - can_send_messages: Some(true), - can_send_media_messages: Some(true), - can_send_other_messages: Some(true), - can_add_web_page_previews: Some(true), }; let actual = serde_json::from_str::(&json).unwrap(); assert_eq!(actual, expected) From cd5d6fd6b9dc8a640c5824e273c6c7cd364e5375 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 23 Sep 2020 21:52:09 +0300 Subject: [PATCH 101/755] Make `AutoRequest<_>` fused --- src/bot/auto_send.rs | 80 +++++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/src/bot/auto_send.rs b/src/bot/auto_send.rs index 2ccc6cd5..d1d0d458 100644 --- a/src/bot/auto_send.rs +++ b/src/bot/auto_send.rs @@ -4,6 +4,8 @@ use std::{ task::{Context, Poll}, }; +use futures::future::FusedFuture; + use crate::requests::{HasPayload, Output, Request, Requester}; /// Send requests automatically. @@ -74,19 +76,19 @@ impl AutoRequest { } } +/// Data of the `AutoRequest` used to not expose variants (I wish there were +/// private enum variants). #[pin_project::pin_project(project = InnerProj, project_replace = InnerRepl)] enum Inner { - /// Unsent modifiable request + /// Unsent modifiable request. Request(R), - /// Sent request + /// Sent request. Future(#[pin] R::Send), - /// This is mostly redundant variant that is used only to take ownership - /// over `Request(R)` before creating `Future(R::Send)` in - /// `AutoRequest::poll`. + /// Done state. Set after `R::Send::poll` returned `Ready(_)`. /// - /// Practically we don't create this variant anywhere else and it can be - /// ignored. - Tmp, + /// Also used as a temporary replacement to turn pinned `Request(req)` + /// into `Future(req.send())` in `AutoRequest::poll`. + Done, } impl Request for AutoRequest { @@ -98,7 +100,7 @@ impl Request for AutoRequest { match self.0 { Inner::Request(req) => req.send(), Inner::Future(fut) => fut, - Inner::Tmp => tmp_unreachable(), + Inner::Done => done_unreachable(), } } @@ -106,7 +108,7 @@ impl Request for AutoRequest { match &self.0 { Inner::Request(req) => req.send_ref(), Inner::Future(_) => already_polled(), - Inner::Tmp => tmp_unreachable(), + Inner::Done => done_unreachable(), } } } @@ -118,7 +120,7 @@ impl HasPayload for AutoRequest { match &mut self.0 { Inner::Request(req) => req.payload_mut(), Inner::Future(_) => already_polled(), - Inner::Tmp => tmp_unreachable(), + Inner::Done => done_unreachable(), } } @@ -126,7 +128,7 @@ impl HasPayload for AutoRequest { match &self.0 { Inner::Request(req) => req.payload_ref(), Inner::Future(_) => already_polled(), - Inner::Tmp => tmp_unreachable(), + Inner::Done => done_unreachable(), } } } @@ -135,30 +137,54 @@ impl Future for AutoRequest { type Output = Result, R::Err>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.as_mut().project(); + let mut this: Pin<&mut Inner<_>> = self.as_mut().project().0; - if let InnerProj::Future(fut) = this.0.as_mut().project() { - return fut.poll(cx); + match this.as_mut().project() { + // Poll underling future + InnerProj::Future(fut) => { + let res = futures::ready!(fut.poll(cx)); + // We've got the result, so we set the state to done + this.set(Inner::Done); + Poll::Ready(res) + }, + // This future is fused + InnerProj::Done => Poll::Pending, + // The `AutoRequest` future was polled for the first time after + // creation. We need to transform it into sent form by calling + // `R::send` and doing some magic around Pin. + InnerProj::Request(_) => { + // Replace `Request(_)` by `Done(_)` to obtain ownership over + // the request. + let inner = this.as_mut().project_replace(Inner::Done); + // Map Request(req) to `Future(req.send())` + let inner = match inner { + InnerRepl::Request(req) => Inner::Future(req.send()), + // Practically this is unreachable, because we've just checked for + // both `Future(_)` and `Done` variants. + InnerRepl::Future(_) | InnerRepl::Done => done_unreachable(), + }; + // Set the resulting `Future(_)` back to pin + this.set(inner); + + // poll self again, this time + self.poll(cx) + } } + } +} - let inner = this.0.as_mut().project_replace(Inner::Tmp); - let inner = match inner { - InnerRepl::Request(req) => Inner::Future(req.send()), - // Practically this is unreachable - InnerRepl::Future(_) | InnerRepl::Tmp => tmp_unreachable(), - }; - this.0.as_mut().project_replace(inner); - - self.poll(cx) +impl FusedFuture for AutoRequest { + fn is_terminated(&self) -> bool { + matches!(&self.0, Inner::Done) } } #[inline(never)] -fn tmp_unreachable() -> ! { - unreachable!("tmp is not created outside of AutoRequest::poll") +fn done_unreachable() -> ! { + unreachable!("future is completed and as such doesn't provide any functionality") } #[inline(never)] fn already_polled() -> ! { - panic!("AutoRequest was already polled") + panic!("AutoRequest was already polled once") } From f4b11095a7c90886646291a471673228cde64959 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 23 Sep 2020 22:06:23 +0300 Subject: [PATCH 102/755] fmt --- src/bot/auto_send.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bot/auto_send.rs b/src/bot/auto_send.rs index d1d0d458..757ad2af 100644 --- a/src/bot/auto_send.rs +++ b/src/bot/auto_send.rs @@ -146,7 +146,7 @@ impl Future for AutoRequest { // We've got the result, so we set the state to done this.set(Inner::Done); Poll::Ready(res) - }, + } // This future is fused InnerProj::Done => Poll::Pending, // The `AutoRequest` future was polled for the first time after From 57afa34609395cad4bf01b7dbfb2893ca631e1a0 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 23 Sep 2020 23:44:53 +0300 Subject: [PATCH 103/755] rephase --- src/bot/auto_send.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bot/auto_send.rs b/src/bot/auto_send.rs index 757ad2af..ad082fa5 100644 --- a/src/bot/auto_send.rs +++ b/src/bot/auto_send.rs @@ -154,7 +154,7 @@ impl Future for AutoRequest { // `R::send` and doing some magic around Pin. InnerProj::Request(_) => { // Replace `Request(_)` by `Done(_)` to obtain ownership over - // the request. + // the former. let inner = this.as_mut().project_replace(Inner::Done); // Map Request(req) to `Future(req.send())` let inner = match inner { From 429ab0b74ac1ac21408cf8c24df44b17925d15eb Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 1 Oct 2020 21:00:02 +0300 Subject: [PATCH 104/755] Apply suggestions from code review Co-authored-by: Temirkhan Myrzamadi --- src/bot/auto_send.rs | 11 ++++++----- src/lib.rs | 5 +++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/bot/auto_send.rs b/src/bot/auto_send.rs index ad082fa5..ce3593c7 100644 --- a/src/bot/auto_send.rs +++ b/src/bot/auto_send.rs @@ -18,7 +18,7 @@ use crate::requests::{HasPayload, Output, Request, Requester}; /// 1. This wrapper should be the most outer i.e.: `AutoSend>` /// will automatically send requests, while `CacheMe>` - won't. /// 2. After first call to `poll` on a request you will unable to access payload -/// nor could you use [`send_ref`](Request::send_ref) +/// nor could you use [`send_ref`](Request::send_ref). /// /// ## Examples /// @@ -48,7 +48,7 @@ impl AutoSend { Self { bot: inner } } - /// Allows to access inner bot + /// Allows to access the inner bot. pub fn inner(&self) -> &B { &self.bot } @@ -143,11 +143,12 @@ impl Future for AutoRequest { // Poll underling future InnerProj::Future(fut) => { let res = futures::ready!(fut.poll(cx)); - // We've got the result, so we set the state to done + // We've got the result, so we set the state to done. this.set(Inner::Done); Poll::Ready(res) } - // This future is fused + + // This future is fused. InnerProj::Done => Poll::Pending, // The `AutoRequest` future was polled for the first time after // creation. We need to transform it into sent form by calling @@ -163,7 +164,7 @@ impl Future for AutoRequest { // both `Future(_)` and `Done` variants. InnerRepl::Future(_) | InnerRepl::Done => done_unreachable(), }; - // Set the resulting `Future(_)` back to pin + // Set the resulting `Future(_)` back to pin. this.set(inner); // poll self again, this time diff --git a/src/lib.rs b/src/lib.rs index 3c26cd26..214553c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,9 +13,10 @@ //#![deny(missing_docs)] #[macro_use] -mod local_macros; // internal helper macros +// The internal helper macros. +mod local_macros; -// FIXME(waffle): rethink modules, find a place for wrappers +// FIXME(waffle): rethink modules, find a place for wrappers. pub use self::{ bot::{AutoSend, Bot, BotBuilder, CacheMe}, errors::{ApiErrorKind, DownloadError, KnownApiErrorKind, RequestError}, From a7066e5e11911b88e551cc2138c2ce11680bdb3d Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 1 Oct 2020 21:08:38 +0300 Subject: [PATCH 105/755] Apply suggestions from code review Co-authored-by: Temirkhan Myrzamadi --- src/bot/auto_send.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bot/auto_send.rs b/src/bot/auto_send.rs index ce3593c7..ed1f489c 100644 --- a/src/bot/auto_send.rs +++ b/src/bot/auto_send.rs @@ -53,7 +53,7 @@ impl AutoSend { &self.bot } - /// Unwraps inner bot + /// Unwraps the inner bot. pub fn into_inner(self) -> B { self.bot } @@ -80,9 +80,9 @@ impl AutoRequest { /// private enum variants). #[pin_project::pin_project(project = InnerProj, project_replace = InnerRepl)] enum Inner { - /// Unsent modifiable request. + /// An unsent modifiable request. Request(R), - /// Sent request. + /// A sent request. Future(#[pin] R::Send), /// Done state. Set after `R::Send::poll` returned `Ready(_)`. /// @@ -140,7 +140,7 @@ impl Future for AutoRequest { let mut this: Pin<&mut Inner<_>> = self.as_mut().project().0; match this.as_mut().project() { - // Poll underling future + // Poll the underling future. InnerProj::Future(fut) => { let res = futures::ready!(fut.poll(cx)); // We've got the result, so we set the state to done. @@ -157,7 +157,7 @@ impl Future for AutoRequest { // Replace `Request(_)` by `Done(_)` to obtain ownership over // the former. let inner = this.as_mut().project_replace(Inner::Done); - // Map Request(req) to `Future(req.send())` + // Map Request(req) to `Future(req.send())`. let inner = match inner { InnerRepl::Request(req) => Inner::Future(req.send()), // Practically this is unreachable, because we've just checked for From 269859eba93b7d8bafa9cf5c6f99b4735ab83b6b Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 1 Oct 2020 21:11:28 +0300 Subject: [PATCH 106/755] Update src/bot/auto_send.rs Co-authored-by: Temirkhan Myrzamadi --- src/bot/auto_send.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bot/auto_send.rs b/src/bot/auto_send.rs index ed1f489c..e64b893c 100644 --- a/src/bot/auto_send.rs +++ b/src/bot/auto_send.rs @@ -167,7 +167,7 @@ impl Future for AutoRequest { // Set the resulting `Future(_)` back to pin. this.set(inner); - // poll self again, this time + // Poll `self`. This time another brunch will be executed, returning `Poll`. self.poll(cx) } } From 8271150218eb29e74614e52593356518442f0646 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 29 Sep 2020 00:45:04 +0300 Subject: [PATCH 107/755] add SendMessage request --- src/bot/api.rs | 10 ++++ src/bot/cache_me.rs | 12 ++++ src/payloads/mod.rs | 2 + src/payloads/send_message.rs | 112 +++++++++++++++++++++++++++++++++++ src/payloads/setters.rs | 1 + src/requests/requester.rs | 19 +++++- 6 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 src/payloads/send_message.rs diff --git a/src/bot/api.rs b/src/bot/api.rs index be6b21f5..721f8a71 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -1710,4 +1710,14 @@ impl Requester for Bot { fn get_me(&self) -> JsonRequest { Self::GetMe::new(self.clone(), payloads::GetMe::new()) } + + type SendMessage = JsonRequest; + + fn send_message(&self, chat_id: C, text: T) -> JsonRequest + where + C: Into, + T: Into + { + Self::SendMessage::new(self.clone(), payloads::SendMessage::new(chat_id, text)) + } } diff --git a/src/bot/cache_me.rs b/src/bot/cache_me.rs index e4a4623d..51f1ba76 100644 --- a/src/bot/cache_me.rs +++ b/src/bot/cache_me.rs @@ -13,6 +13,8 @@ use crate::{ requests::{HasPayload, Request, Requester}, types::User, }; +use crate::payloads::SendMessage; +use crate::types::ChatId; /// `get_me` cache. /// @@ -66,6 +68,16 @@ impl Requester for CacheMe { ), } } + + type SendMessage = B::SendMessage; + + fn send_message(&self, chat_id: C, text: T) -> Self::SendMessage + where + C: Into, + T: Into + { + self.bot.send_message(chat_id, text) + } } pub struct CachedMeRequest>(Inner, GetMe); diff --git a/src/payloads/mod.rs b/src/payloads/mod.rs index e01013c2..c468e562 100644 --- a/src/payloads/mod.rs +++ b/src/payloads/mod.rs @@ -1,5 +1,7 @@ pub mod setters; mod get_me; +mod send_message; pub use get_me::{GetMe, GetMeSetters}; +pub use send_message::{SendMessage, SendMessageSetters}; diff --git a/src/payloads/send_message.rs b/src/payloads/send_message.rs new file mode 100644 index 00000000..5d483525 --- /dev/null +++ b/src/payloads/send_message.rs @@ -0,0 +1,112 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + requests::{HasPayload, Payload}, + types::{ChatId, Message, ParseMode, ReplyMarkup} +}; + +/// Use this method to send text messages. +/// +/// On success, the sent [`Message`] is returned. +/// +/// [`Message`]: crate::types::Message +#[serde_with_macros::skip_serializing_none] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)] +pub struct SendMessage { + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`) + pub chat_id: ChatId, + /// Text of the message to be sent + pub text: String, + /// Send [Markdown] or [HTML], if you want Telegram apps to show + /// [bold, italic, fixed-width text or inline URLs] in your bot's message. + /// + /// [Markdown]: crate::types::ParseMode::Markdown + /// [HTML]: crate::types::ParseMode::HTML + /// [bold, italic, fixed-width text or inline URLs]: + /// crate::types::ParseMode + pub parse_mode: Option, + /// Disables link previews for links in this message + pub disable_web_page_preview: Option, + /// Sends the message silently. + /// Users will receive a notification with no sound. + pub disable_notification: Option, + /// If the message is a reply, [id] of the original message + /// + /// [id]: crate::types::Message::id + pub reply_to_message_id: Option, + /// Additional interface options. + pub reply_markup: Option, +} + +impl Payload for SendMessage { + type Output = Message; + + const NAME: &'static str = "sendMessage"; +} + +impl SendMessage { + pub fn new(chat_id: C, text: T) -> Self + where + C: Into, + T: Into, + { + SendMessage { + chat_id: chat_id.into(), + text: text.into(), + parse_mode: None, + disable_web_page_preview: None, + disable_notification: None, + reply_to_message_id: None, + reply_markup: None, + } + } +} + +pub trait SendMessageSetters: HasPayload + Sized { + fn chat_id(mut self, value: T) -> Self + where + T: Into, + { + self.payload_mut().chat_id = value.into(); + self + } + + fn text(mut self, value: T) -> Self + where + T: Into, // TODO: into? + { + self.payload_mut().text = value.into(); + self + } + + fn parse_mode(mut self, value: ParseMode) -> Self { + self.payload_mut().parse_mode = Some(value); + self + } + + fn disable_web_page_preview(mut self, value: bool) -> Self { + self.payload_mut().disable_web_page_preview = Some(value); + self + } + + fn disable_notification(mut self, value: bool) -> Self { + self.payload_mut().disable_notification = Some(value); + self + } + + fn reply_to_message_id(mut self, value: i32) -> Self { + self.payload_mut().reply_to_message_id = Some(value); + self + } + + fn reply_markup(mut self, value: T) -> Self + where + T: Into, + { + self.payload_mut().reply_markup = Some(value.into()); + self + } +} + +impl

SendMessageSetters for P where P: HasPayload {} diff --git a/src/payloads/setters.rs b/src/payloads/setters.rs index c7cfd823..646f1a88 100644 --- a/src/payloads/setters.rs +++ b/src/payloads/setters.rs @@ -1 +1,2 @@ pub use crate::payloads::GetMeSetters as _; +pub use crate::payloads::SendMessageSetters as _; diff --git a/src/requests/requester.rs b/src/requests/requester.rs index 8390b786..69dd1d25 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -1,4 +1,11 @@ -use crate::{payloads::GetMe, requests::Request}; +use crate::{ + payloads::{ + GetMe, + SendMessage + }, + requests::Request, + types::ChatId, +}; /// The trait implemented by all bots & bot wrappers. /// Essentially a request builder factory (?). @@ -11,5 +18,13 @@ pub trait Requester { /// For telegram documentation of the method see [`GetMe`]. fn get_me(&self) -> Self::GetMe; - // FIXME(waffle): add remaining 69 methods + type SendMessage: Request; + + /// For telegram documentation of the method see [`SendMessage`]. + fn send_message(&self, chat_id: C, text: T) -> Self::SendMessage + where + C: Into, + T: Into; + + // FIXME(waffle): add remaining 68 methods } From 4e412b39e1dcb2e1cd0a76a21de15502160b588d Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 29 Sep 2020 00:45:57 +0300 Subject: [PATCH 108/755] (very bad) initial implementation of throttling --- src/bot/limits.rs | 279 ++++++++++++++++++++++++++++++++++++++++++++++ src/bot/mod.rs | 1 + 2 files changed, 280 insertions(+) create mode 100644 src/bot/limits.rs diff --git a/src/bot/limits.rs b/src/bot/limits.rs new file mode 100644 index 00000000..d73ff850 --- /dev/null +++ b/src/bot/limits.rs @@ -0,0 +1,279 @@ +use crate::Bot; +use std::collections::{VecDeque, HashMap}; +use std::time::Instant; +use std::sync::Arc; +use tokio::sync::Mutex; +use std::cmp::max; +use crate::requests::{Requester, Request, Output, HasPayload, Payload}; +use crate::payloads::{GetMe, SendMessage}; +use crate::types::ChatId; +use tokio::sync::oneshot::{Sender, Receiver, channel}; +use std::future::Future; +use futures::task::{Context, Poll}; +use pin_project::__private::Pin; +use core::time::Duration; +use futures::{TryFutureExt, StreamExt, FutureExt}; +use tokio::time::{delay_until, delay_for}; +use futures::stream::FuturesUnordered; +use futures::executor::block_on; +use futures::future::join3; + +const MINUTE: Duration = Duration::from_secs(10); +const SECOND: Duration = Duration::from_secs(1); +const DELAY: Duration = Duration::from_millis(250); // second/4 + +pub struct Limits { + /// Allowed messages in one chat per second + pub chat_s: u32, + /// Allowed messages per second + pub overall_s: u32, + /// Allowed messages in one chat per minute + pub chat_m: u32, +} + +// https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this +impl Default for Limits { + fn default() -> Self { + Self { + chat_s: 1, + overall_s: 30, + chat_m: 20, + } + } +} + +pub struct Limited { + // Some fields are probably only needed by the worker + //limits: Limits, + bot: B, + queue: Arc)>>>, // FIXME: struct with fast remove and add + history: Arc>>, + hchats: Arc>>, +} + +async fn worker( + limits: Limits, + queue: Arc)>>>, + history: Arc>>, + hchats: Arc>>, +) { + // FIXME: remove unnecessary ChatId clones + loop { + println!("1"); + let mut history = history.lock().await; + let mut hchats = hchats.lock().await; + let mut queue = queue.lock().await; + + let now = dbg!(Instant::now()); + let min_back = now - MINUTE; + let sec_back = now - SECOND; + + println!("2"); + + // make history and hchats up-to-date + while let Some((_, time)) = history.front() { + // history is sorted, we found first up-to-date thing + if time >= &min_back { break; } + + if let Some((chat, _)) = history.pop_front() { + hchats + .entry(chat) + .and_modify(|count| { *count -= 1; }); // TODO: remove entries with count == 0 + } + } + + // as truncates which is ok since in case of truncation it would always be >= limits.overall_s + let mut allowed = limits.overall_s.saturating_sub(dbg!(&history).iter().take_while(|(_, time)| time > &sec_back).count() as u32); + + if allowed == 0 { + delay_for(DELAY).await; + continue; + } + + println!("3"); + + let mut hchats_s = HashMap::new(); + for (chat, _) in history.iter().take_while(|(_, time)| time > &sec_back) { + *hchats_s + .entry(chat.clone()) + .or_insert(0) += 1; + } + + + dbg!(&hchats_s); + dbg!(&hchats); + + dbg!(allowed); + + let mut i = 0; + while allowed > 0 && i < queue.len() { + let chat = &queue[i].0; + + if dbg!(hchats_s + .get(chat) + .copied() + .unwrap_or(0) < limits.chat_s) && + dbg!(hchats + .get(chat) + .copied() + .unwrap_or(0) < limits.chat_m) + { + let chat = chat.clone(); + *hchats_s.entry(chat.clone()).or_insert(0) += 1; + *hchats.entry(chat.clone()).or_insert(0) += 1; + + println!("worker send"); + dbg!(&hchats_s); + dbg!(&hchats); + history.push_back((chat, Instant::now())); + queue.remove(i).unwrap().1.send(()); + allowed -= 1; + dbg!(allowed); + } else { + i += 1; + } + } + + delay_for(DELAY).await; + } +} + +impl Limited { + pub fn new(bot: B, limits: Limits) -> (Self, impl Future) { + let history = Arc::new(Mutex::new(VecDeque::with_capacity( + max(limits.chat_s, max(limits.overall_s, limits.chat_m)) as _ + ))); + + let queue = Arc::new(Mutex::new(VecDeque::new())); + let hchats = Arc::new(Mutex::new(HashMap::new())); + + let worker = worker( + limits, + Arc::clone(&queue), + Arc::clone(&history), + Arc::clone(&hchats), + ); + + let this = Self { + //limits, + bot, + history, + queue, + hchats, + }; + + (this, worker) + } +} + +impl Requester for Limited { + type GetMe = B::GetMe; + + fn get_me(&self) -> Self::GetMe { + self.bot.get_me() + } + + type SendMessage = LimitedRequest; + + fn send_message(&self, chat_id: C, text: T) -> Self::SendMessage + where + C: Into, + T: Into + { + LimitedRequest(self.bot.send_message(chat_id, text), Arc::clone(&self.queue)) + } +} + +pub trait GetChatId { + // FIXME(waffle): add note about false negatives with ChatId::Username + fn get_chat_id(&self) -> &ChatId; +} + +impl GetChatId for SendMessage { + fn get_chat_id(&self) -> &ChatId { + &self.chat_id + } +} + +pub struct LimitedRequest(R, Arc)>>>); + +impl HasPayload for LimitedRequest { + type Payload = R::Payload; + + fn payload_mut(&mut self) -> &mut Self::Payload { + self.0.payload_mut() + } + + fn payload_ref(&self) -> &Self::Payload { + self.0.payload_ref() + } +} + +impl Request for LimitedRequest +where + ::Payload: GetChatId, +{ + type Err = R::Err; + type Send = LimitedSend; + type SendRef = LimitedSend; + + fn send(self) -> Self::Send { + let (tx, rx) = channel(); + // FIXME + let mut g = block_on(self.1.lock()); + g.push_back((self.0.payload_ref().get_chat_id().clone(), tx)); + LimitedSend::Pending { + request: self.0, + wait: rx, + } + } + + fn send_ref(&self) -> Self::SendRef { + unimplemented!() + } +} + +#[pin_project::pin_project(project = SendProj, project_replace = SendRepl)] +pub enum LimitedSend { + Pending { + request: R, + #[pin] + wait: Receiver<()>, + }, + Sent { + #[pin] + fut: R::Send, + }, + Done, +} + +impl Future for LimitedSend { + type Output = Result, R::Err>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + println!("poll"); + match self.as_mut().project() { + SendProj::Pending { request: _, wait } => match wait.poll(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(r) => { + println!("pending-ready"); + // FIXME(waffle): remove unwrap + r.unwrap(); + if let SendRepl::Pending { request, wait: _ } = self.as_mut().project_replace(LimitedSend::Done) { + self.as_mut().project_replace(LimitedSend::Sent { fut: request.send() }); + } + + self.poll(cx) + } + }, + SendProj::Sent { fut } => { + println!("sent"); + let res = futures::ready!(fut.poll(cx)); + println!("sent-ready"); + self.set(LimitedSend::Done); + Poll::Ready(res) + } + SendProj::Done => Poll::Pending, + } + } +} diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 3347c932..54154cad 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -17,6 +17,7 @@ mod api; mod auto_send; mod cache_me; mod download; +mod limits; pub use auto_send::AutoSend; pub use cache_me::CacheMe; From f270613e7ec5f4844baca766148c8465d8df15a3 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 29 Sep 2020 22:17:59 +0300 Subject: [PATCH 109/755] [throttle] refactor#0 - Move all data to the worker making it unshared. - Use mpsc channel to update requests queue - Yield if there are no requests in queue - Remove `:0` entries from `hchats` - Remove debug prints - Remove use of `block_on` --- src/bot/limits.rs | 179 +++++++++++++++++++++++++++++----------------- 1 file changed, 115 insertions(+), 64 deletions(-) diff --git a/src/bot/limits.rs b/src/bot/limits.rs index d73ff850..539c6dee 100644 --- a/src/bot/limits.rs +++ b/src/bot/limits.rs @@ -2,7 +2,7 @@ use crate::Bot; use std::collections::{VecDeque, HashMap}; use std::time::Instant; use std::sync::Arc; -use tokio::sync::Mutex; +use tokio::sync::{Mutex, mpsc}; use std::cmp::max; use crate::requests::{Requester, Request, Output, HasPayload, Payload}; use crate::payloads::{GetMe, SendMessage}; @@ -17,8 +17,11 @@ use tokio::time::{delay_until, delay_for}; use futures::stream::FuturesUnordered; use futures::executor::block_on; use futures::future::join3; +use futures::future::ready; -const MINUTE: Duration = Duration::from_secs(10); +// FIXME: rename to Throttle + +const MINUTE: Duration = Duration::from_secs(50); // FIXME: min = sec * 10 only in tests const SECOND: Duration = Duration::from_secs(1); const DELAY: Duration = Duration::from_millis(250); // second/4 @@ -43,42 +46,52 @@ impl Default for Limits { } pub struct Limited { - // Some fields are probably only needed by the worker - //limits: Limits, bot: B, - queue: Arc)>>>, // FIXME: struct with fast remove and add - history: Arc>>, - hchats: Arc>>, + queue: mpsc::Sender<(ChatId, Sender<()>)>, } async fn worker( limits: Limits, - queue: Arc)>>>, - history: Arc>>, - hchats: Arc>>, + mut queue_rx: mpsc::Receiver<(ChatId, Sender<()>)>, ) { - // FIXME: remove unnecessary ChatId clones - loop { - println!("1"); - let mut history = history.lock().await; - let mut hchats = hchats.lock().await; - let mut queue = queue.lock().await; + // FIXME: use spawn_blocking? - let now = dbg!(Instant::now()); + // FIXME: remove unnecessary ChatId clones + + // FIXME: struct with fast random remove and append-to-the-end + let mut queue: VecDeque<(ChatId, Sender<()>)> = VecDeque::new(); // FIXME: with_cap + + let mut history: VecDeque<(ChatId, Instant)> = VecDeque::new(); + // hchats[chat] = history.iter().filter(|(c, _)| c == chat).count() + let mut hchats: HashMap = HashMap::new(); + + loop { + // If there are no pending requests we are just waiting + if queue.is_empty() { + queue.push_back(queue_rx.recv().await.unwrap()); + } + + // update local queue with latest requests + while let Ok(e) = queue_rx.try_recv() { + // FIXME: properly check for errors (stop when the bot's sender is dropped?) + queue.push_back(e) + } + + let now = Instant::now(); let min_back = now - MINUTE; let sec_back = now - SECOND; - println!("2"); - // make history and hchats up-to-date while let Some((_, time)) = history.front() { // history is sorted, we found first up-to-date thing if time >= &min_back { break; } if let Some((chat, _)) = history.pop_front() { - hchats + if let Entry::Occupied(entry) = hchats .entry(chat) - .and_modify(|count| { *count -= 1; }); // TODO: remove entries with count == 0 + .and_modify(|count| { *count -= 1; }) { + if entry.get() == 0 { entry.remove_entry(); } + } } } @@ -90,8 +103,6 @@ async fn worker( continue; } - println!("3"); - let mut hchats_s = HashMap::new(); for (chat, _) in history.iter().take_while(|(_, time)| time > &sec_back) { *hchats_s @@ -99,36 +110,26 @@ async fn worker( .or_insert(0) += 1; } - - dbg!(&hchats_s); - dbg!(&hchats); - - dbg!(allowed); - let mut i = 0; while allowed > 0 && i < queue.len() { let chat = &queue[i].0; - if dbg!(hchats_s + if hchats_s .get(chat) .copied() - .unwrap_or(0) < limits.chat_s) && - dbg!(hchats + .unwrap_or(0) < limits.chat_s && + hchats .get(chat) .copied() - .unwrap_or(0) < limits.chat_m) + .unwrap_or(0) < limits.chat_m { let chat = chat.clone(); *hchats_s.entry(chat.clone()).or_insert(0) += 1; *hchats.entry(chat.clone()).or_insert(0) += 1; - println!("worker send"); - dbg!(&hchats_s); - dbg!(&hchats); history.push_back((chat, Instant::now())); queue.remove(i).unwrap().1.send(()); allowed -= 1; - dbg!(allowed); } else { i += 1; } @@ -140,27 +141,15 @@ async fn worker( impl Limited { pub fn new(bot: B, limits: Limits) -> (Self, impl Future) { - let history = Arc::new(Mutex::new(VecDeque::with_capacity( - max(limits.chat_s, max(limits.overall_s, limits.chat_m)) as _ - ))); - - let queue = Arc::new(Mutex::new(VecDeque::new())); - let hchats = Arc::new(Mutex::new(HashMap::new())); + // FIXME: just a random number, currently + let (queue_tx, queue_rx) = mpsc::channel(130); let worker = worker( limits, - Arc::clone(&queue), - Arc::clone(&history), - Arc::clone(&hchats), + queue_rx, ); - let this = Self { - //limits, - bot, - history, - queue, - hchats, - }; + let this = Self { bot, queue: queue_tx, }; (this, worker) } @@ -180,7 +169,7 @@ impl Requester for Limited { C: Into, T: Into { - LimitedRequest(self.bot.send_message(chat_id, text), Arc::clone(&self.queue)) + LimitedRequest(self.bot.send_message(chat_id, text), self.queue.clone()) } } @@ -195,7 +184,7 @@ impl GetChatId for SendMessage { } } -pub struct LimitedRequest(R, Arc)>>>); +pub struct LimitedRequest(R, mpsc::Sender<(ChatId, Sender<()>)>); impl HasPayload for LimitedRequest { type Payload = R::Payload; @@ -217,13 +206,12 @@ where type Send = LimitedSend; type SendRef = LimitedSend; - fn send(self) -> Self::Send { + fn send(mut self) -> Self::Send { let (tx, rx) = channel(); - // FIXME - let mut g = block_on(self.1.lock()); - g.push_back((self.0.payload_ref().get_chat_id().clone(), tx)); - LimitedSend::Pending { + let send = self.1.send_t((self.0.payload_ref().get_chat_id().clone(), tx)); + LimitedSend::Registering { request: self.0, + send, wait: rx, } } @@ -235,6 +223,12 @@ where #[pin_project::pin_project(project = SendProj, project_replace = SendRepl)] pub enum LimitedSend { + Registering { + request: R, + #[pin] + send: ChanSend, + wait: Receiver<()>, + }, Pending { request: R, #[pin] @@ -251,12 +245,22 @@ impl Future for LimitedSend { type Output = Result, R::Err>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - println!("poll"); match self.as_mut().project() { + SendProj::Registering { request: _, send, wait: _ } => match send.poll(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(r) => { + // FIXME(waffle): remove unwrap + r.unwrap(); + if let SendRepl::Registering { request, send: _, wait } = self.as_mut().project_replace(LimitedSend::Done) { + self.as_mut().project_replace(LimitedSend::Pending { request, wait }); + } + + self.poll(cx) + } + }, SendProj::Pending { request: _, wait } => match wait.poll(cx) { Poll::Pending => Poll::Pending, Poll::Ready(r) => { - println!("pending-ready"); // FIXME(waffle): remove unwrap r.unwrap(); if let SendRepl::Pending { request, wait: _ } = self.as_mut().project_replace(LimitedSend::Done) { @@ -267,9 +271,7 @@ impl Future for LimitedSend { } }, SendProj::Sent { fut } => { - println!("sent"); let res = futures::ready!(fut.poll(cx)); - println!("sent-ready"); self.set(LimitedSend::Done); Poll::Ready(res) } @@ -277,3 +279,52 @@ impl Future for LimitedSend { } } } + +use chan_send::{ChanSend, SendTy as _}; +use crate::bot::limits::chan_send::SendTy; +use std::collections::hash_map::Entry; + +mod chan_send { + use tokio::sync::mpsc; + use crate::types::ChatId; + use tokio::sync::oneshot::Sender; + use std::future::Future; + use futures::task::{Context, Poll}; + use pin_project::__private::Pin; + use tokio::sync::mpsc::error::SendError; + + pub(crate) trait SendTy { + fn send_t(self, val: (ChatId, Sender<()>)) -> ChanSend; + } + + #[pin_project::pin_project] + pub/*(crate) */struct ChanSend(#[pin] Inner); // FIXME + + #[cfg(not(feature = "nightly"))] + type Inner = Pin)>>>>>; + #[cfg(feature = "nightly")] + type Inner = impl Future)>>>; + + impl SendTy for mpsc::Sender<(ChatId, Sender<()>)> { + fn send_t(mut self, val: (ChatId, Sender<()>)) -> ChanSend { + #[cfg(feature = "nightly")] + { + fn def(mut sender: mpsc::Sender<(ChatId, Sender<()>)>, val: (ChatId, Sender<()>)) -> Inner { + async move { sender.send(val).await } + } + return ChanSend(def(self, val)); + } + #[cfg(not(feature = "nightly"))] + return ChanSend(Box::pin(async move { self.send(val).await })); + } + } + + impl Future for ChanSend { + type Output = Result<(), SendError<(ChatId, Sender<()>)>>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.project().0.poll(cx) + } + } + +} \ No newline at end of file From 3f9db1f31beda47dc07447ba7d0d03c882a59dea Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 30 Sep 2020 19:21:35 +0300 Subject: [PATCH 110/755] [throttle] queue optimizations#0 --- src/bot/limits.rs | 60 ++++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/src/bot/limits.rs b/src/bot/limits.rs index 539c6dee..be8e3a12 100644 --- a/src/bot/limits.rs +++ b/src/bot/limits.rs @@ -59,7 +59,7 @@ async fn worker( // FIXME: remove unnecessary ChatId clones // FIXME: struct with fast random remove and append-to-the-end - let mut queue: VecDeque<(ChatId, Sender<()>)> = VecDeque::new(); // FIXME: with_cap + let mut queue: Vec)>> = Vec::new(); // FIXME: with_cap let mut history: VecDeque<(ChatId, Instant)> = VecDeque::new(); // hchats[chat] = history.iter().filter(|(c, _)| c == chat).count() @@ -68,13 +68,13 @@ async fn worker( loop { // If there are no pending requests we are just waiting if queue.is_empty() { - queue.push_back(queue_rx.recv().await.unwrap()); + queue.push(Some(queue_rx.recv().await.unwrap())); } // update local queue with latest requests while let Ok(e) = queue_rx.try_recv() { // FIXME: properly check for errors (stop when the bot's sender is dropped?) - queue.push_back(e) + queue.push(Some(e)) } let now = Instant::now(); @@ -90,13 +90,13 @@ async fn worker( if let Entry::Occupied(entry) = hchats .entry(chat) .and_modify(|count| { *count -= 1; }) { - if entry.get() == 0 { entry.remove_entry(); } + if *entry.get() == 0 { entry.remove_entry(); } } } } // as truncates which is ok since in case of truncation it would always be >= limits.overall_s - let mut allowed = limits.overall_s.saturating_sub(dbg!(&history).iter().take_while(|(_, time)| time > &sec_back).count() as u32); + let mut allowed = limits.overall_s.saturating_sub(history.iter().take_while(|(_, time)| time > &sec_back).count() as u32); if allowed == 0 { delay_for(DELAY).await; @@ -110,30 +110,45 @@ async fn worker( .or_insert(0) += 1; } - let mut i = 0; - while allowed > 0 && i < queue.len() { - let chat = &queue[i].0; - - if hchats_s - .get(chat) - .copied() - .unwrap_or(0) < limits.chat_s && - hchats + let mut empty = 0; + for i in 0..queue.len() { + let chat = &queue[i].as_ref().unwrap().0; + let cond = { + hchats_s .get(chat) .copied() - .unwrap_or(0) < limits.chat_m - { - let chat = chat.clone(); - *hchats_s.entry(chat.clone()).or_insert(0) += 1; - *hchats.entry(chat.clone()).or_insert(0) += 1; + .unwrap_or(0) < limits.chat_s && + hchats + .get(chat) + .copied() + .unwrap_or(0) < limits.chat_m + }; + + if cond { + { + *hchats_s.entry(chat.clone()).or_insert(0) += 1; + *hchats.entry(chat.clone()).or_insert(0) += 1; + history.push_back((chat.clone(), Instant::now())); + } + queue[i].take().unwrap().1.send(()); - history.push_back((chat, Instant::now())); - queue.remove(i).unwrap().1.send(()); allowed -= 1; + if allowed == 0 { + if empty != i { + // FIXME: this could be more optimal + for j in i..queue.len() { + queue.swap(j, empty); + empty += 1; + } + } + break; + } } else { - i += 1; + queue.swap(i, empty); + empty += 1; } } + queue.truncate(empty); delay_for(DELAY).await; } @@ -283,6 +298,7 @@ impl Future for LimitedSend { use chan_send::{ChanSend, SendTy as _}; use crate::bot::limits::chan_send::SendTy; use std::collections::hash_map::Entry; +use core::mem; mod chan_send { use tokio::sync::mpsc; From 5f5e42054d22da566434962c81a7c133f487de97 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 30 Sep 2020 19:30:04 +0300 Subject: [PATCH 111/755] [throttle] queue optimizations#1 --- Cargo.toml | 3 +++ src/bot/limits.rs | 27 ++++++++++----------------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 12200248..b46e902b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,9 @@ mime = "0.3.16" thiserror = "1.0.20" once_cell = "1.4.0" +# FIXME(waffle): use crates.io once published +vecrem = { git = "https://github.com/WaffleLapkin/vecrem" } + [features] # features those require nightly compiler nightly = [] diff --git a/src/bot/limits.rs b/src/bot/limits.rs index be8e3a12..093ee9ce 100644 --- a/src/bot/limits.rs +++ b/src/bot/limits.rs @@ -59,7 +59,7 @@ async fn worker( // FIXME: remove unnecessary ChatId clones // FIXME: struct with fast random remove and append-to-the-end - let mut queue: Vec)>> = Vec::new(); // FIXME: with_cap + let mut queue: Vec<(ChatId, Sender<()>)> = Vec::new(); // FIXME: with_cap let mut history: VecDeque<(ChatId, Instant)> = VecDeque::new(); // hchats[chat] = history.iter().filter(|(c, _)| c == chat).count() @@ -68,13 +68,13 @@ async fn worker( loop { // If there are no pending requests we are just waiting if queue.is_empty() { - queue.push(Some(queue_rx.recv().await.unwrap())); + queue.push(queue_rx.recv().await.unwrap()); } // update local queue with latest requests while let Ok(e) = queue_rx.try_recv() { // FIXME: properly check for errors (stop when the bot's sender is dropped?) - queue.push(Some(e)) + queue.push(e) } let now = Instant::now(); @@ -110,9 +110,9 @@ async fn worker( .or_insert(0) += 1; } - let mut empty = 0; - for i in 0..queue.len() { - let chat = &queue[i].as_ref().unwrap().0; + let mut queue_rem = queue.removing(); + while let Some(entry) = queue_rem.next() { + let chat = &entry.value().0; let cond = { hchats_s .get(chat) @@ -130,25 +130,17 @@ async fn worker( *hchats.entry(chat.clone()).or_insert(0) += 1; history.push_back((chat.clone(), Instant::now())); } - queue[i].take().unwrap().1.send(()); + entry.remove().1.send(()); allowed -= 1; if allowed == 0 { - if empty != i { - // FIXME: this could be more optimal - for j in i..queue.len() { - queue.swap(j, empty); - empty += 1; - } - } break; } } else { - queue.swap(i, empty); - empty += 1; + entry.skip(); } } - queue.truncate(empty); + drop(queue_rem); delay_for(DELAY).await; } @@ -299,6 +291,7 @@ use chan_send::{ChanSend, SendTy as _}; use crate::bot::limits::chan_send::SendTy; use std::collections::hash_map::Entry; use core::mem; +use vecrem::VecExt; mod chan_send { use tokio::sync::mpsc; From 6714a9c503eb28b060a6fc541468d81de1646679 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 30 Sep 2020 21:39:45 +0300 Subject: [PATCH 112/755] [throttle] clear `hchats_s` instead of creating&dropping every iteration --- src/bot/limits.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/bot/limits.rs b/src/bot/limits.rs index 093ee9ce..87ee3813 100644 --- a/src/bot/limits.rs +++ b/src/bot/limits.rs @@ -58,13 +58,17 @@ async fn worker( // FIXME: remove unnecessary ChatId clones - // FIXME: struct with fast random remove and append-to-the-end + // FIXME: Make an research about data structures for this queue. + // Currently this is O(n) removing (n = number of elements stayed), + // amortized O(1) push (vec+vecrem). let mut queue: Vec<(ChatId, Sender<()>)> = Vec::new(); // FIXME: with_cap let mut history: VecDeque<(ChatId, Instant)> = VecDeque::new(); // hchats[chat] = history.iter().filter(|(c, _)| c == chat).count() let mut hchats: HashMap = HashMap::new(); + let mut hchats_s = HashMap::new(); + loop { // If there are no pending requests we are just waiting if queue.is_empty() { @@ -99,11 +103,11 @@ async fn worker( let mut allowed = limits.overall_s.saturating_sub(history.iter().take_while(|(_, time)| time > &sec_back).count() as u32); if allowed == 0 { + hchats_s.clear(); delay_for(DELAY).await; continue; } - let mut hchats_s = HashMap::new(); for (chat, _) in history.iter().take_while(|(_, time)| time > &sec_back) { *hchats_s .entry(chat.clone()) @@ -142,6 +146,7 @@ async fn worker( } drop(queue_rem); + hchats_s.clear(); delay_for(DELAY).await; } } From b38e35b63095bdbe6d9b1aae1002d40b9428edd7 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 30 Sep 2020 22:41:11 +0300 Subject: [PATCH 113/755] [throttle] format, docs, modfix and small additions --- Cargo.toml | 2 +- src/bot/api.rs | 2 +- src/bot/cache_me.rs | 6 +- src/bot/limits.rs | 196 +++++++++++++++++++--------------- src/bot/mod.rs | 1 + src/lib.rs | 5 +- src/payloads/send_message.rs | 2 +- src/payloads/setters.rs | 3 +- src/requests/requester.rs | 5 +- src/requests/requester_ext.rs | 19 +++- 10 files changed, 141 insertions(+), 100 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b46e902b..01f3f1ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ thiserror = "1.0.20" once_cell = "1.4.0" # FIXME(waffle): use crates.io once published -vecrem = { git = "https://github.com/WaffleLapkin/vecrem" } +vecrem = { git = "https://github.com/WaffleLapkin/vecrem", rev = "6b9b6f42342df8b75548c6ed387072ff235429b1" } [features] # features those require nightly compiler diff --git a/src/bot/api.rs b/src/bot/api.rs index 721f8a71..5e2cb0ad 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -1716,7 +1716,7 @@ impl Requester for Bot { fn send_message(&self, chat_id: C, text: T) -> JsonRequest where C: Into, - T: Into + T: Into, { Self::SendMessage::new(self.clone(), payloads::SendMessage::new(chat_id, text)) } diff --git a/src/bot/cache_me.rs b/src/bot/cache_me.rs index 51f1ba76..8a06ebf2 100644 --- a/src/bot/cache_me.rs +++ b/src/bot/cache_me.rs @@ -11,10 +11,8 @@ use once_cell::sync::OnceCell; use crate::{ payloads::GetMe, requests::{HasPayload, Request, Requester}, - types::User, + types::{ChatId, User}, }; -use crate::payloads::SendMessage; -use crate::types::ChatId; /// `get_me` cache. /// @@ -74,7 +72,7 @@ impl Requester for CacheMe { fn send_message(&self, chat_id: C, text: T) -> Self::SendMessage where C: Into, - T: Into + T: Into, { self.bot.send_message(chat_id, text) } diff --git a/src/bot/limits.rs b/src/bot/limits.rs index 87ee3813..9b74ae72 100644 --- a/src/bot/limits.rs +++ b/src/bot/limits.rs @@ -1,30 +1,35 @@ -use crate::Bot; -use std::collections::{VecDeque, HashMap}; -use std::time::Instant; -use std::sync::Arc; -use tokio::sync::{Mutex, mpsc}; -use std::cmp::max; -use crate::requests::{Requester, Request, Output, HasPayload, Payload}; -use crate::payloads::{GetMe, SendMessage}; -use crate::types::ChatId; -use tokio::sync::oneshot::{Sender, Receiver, channel}; -use std::future::Future; -use futures::task::{Context, Poll}; -use pin_project::__private::Pin; -use core::time::Duration; -use futures::{TryFutureExt, StreamExt, FutureExt}; -use tokio::time::{delay_until, delay_for}; -use futures::stream::FuturesUnordered; -use futures::executor::block_on; -use futures::future::join3; -use futures::future::ready; +use std::{ + collections::{hash_map::Entry, HashMap, VecDeque}, + future::Future, + pin::Pin, + time::{Duration, Instant}, +}; -// FIXME: rename to Throttle +use futures::task::{Context, Poll}; +use tokio::{ + sync::{ + mpsc, + oneshot::{channel, Receiver, Sender}, + }, + time::delay_for, +}; +use vecrem::VecExt; + +use crate::{ + bot::limits::chan_send::{ChanSend, SendTy}, + payloads::SendMessage, + requests::{HasPayload, Output, Request, Requester}, + types::ChatId, +}; const MINUTE: Duration = Duration::from_secs(50); // FIXME: min = sec * 10 only in tests const SECOND: Duration = Duration::from_secs(1); const DELAY: Duration = Duration::from_millis(250); // second/4 +/// Telegram request limits. +/// +/// This struct is used in [`Throttle`] +#[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct Limits { /// Allowed messages in one chat per second pub chat_s: u32, @@ -34,26 +39,21 @@ pub struct Limits { pub chat_m: u32, } -// https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this +/// Defaults are taken from [telegram documentation][tgdoc]. +/// +/// [tgdoc]: https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this impl Default for Limits { fn default() -> Self { - Self { - chat_s: 1, - overall_s: 30, - chat_m: 20, - } + Self { chat_s: 1, overall_s: 30, chat_m: 20 } } } -pub struct Limited { +pub struct Throttle { bot: B, queue: mpsc::Sender<(ChatId, Sender<()>)>, } -async fn worker( - limits: Limits, - mut queue_rx: mpsc::Receiver<(ChatId, Sender<()>)>, -) { +async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(ChatId, Sender<()>)>) { // FIXME: use spawn_blocking? // FIXME: remove unnecessary ChatId clones @@ -88,19 +88,26 @@ async fn worker( // make history and hchats up-to-date while let Some((_, time)) = history.front() { // history is sorted, we found first up-to-date thing - if time >= &min_back { break; } + if time >= &min_back { + break; + } if let Some((chat, _)) = history.pop_front() { - if let Entry::Occupied(entry) = hchats - .entry(chat) - .and_modify(|count| { *count -= 1; }) { - if *entry.get() == 0 { entry.remove_entry(); } + if let Entry::Occupied(entry) = hchats.entry(chat).and_modify(|count| { + *count -= 1; + }) { + if *entry.get() == 0 { + entry.remove_entry(); + } } } } - // as truncates which is ok since in case of truncation it would always be >= limits.overall_s - let mut allowed = limits.overall_s.saturating_sub(history.iter().take_while(|(_, time)| time > &sec_back).count() as u32); + // as truncates which is ok since in case of truncation it would always be >= + // limits.overall_s + let mut allowed = limits + .overall_s + .saturating_sub(history.iter().take_while(|(_, time)| time > &sec_back).count() as u32); if allowed == 0 { hchats_s.clear(); @@ -109,23 +116,15 @@ async fn worker( } for (chat, _) in history.iter().take_while(|(_, time)| time > &sec_back) { - *hchats_s - .entry(chat.clone()) - .or_insert(0) += 1; + *hchats_s.entry(chat.clone()).or_insert(0) += 1; } let mut queue_rem = queue.removing(); while let Some(entry) = queue_rem.next() { let chat = &entry.value().0; let cond = { - hchats_s - .get(chat) - .copied() - .unwrap_or(0) < limits.chat_s && - hchats - .get(chat) - .copied() - .unwrap_or(0) < limits.chat_m + hchats_s.get(chat).copied().unwrap_or(0) < limits.chat_s + && hchats.get(chat).copied().unwrap_or(0) < limits.chat_m }; if cond { @@ -151,37 +150,70 @@ async fn worker( } } -impl Limited { +impl Throttle { + /// Creates new [`Throttle`] alongside with worker future. + /// + /// Note: [`Throttle`] will only send requests if returned worker is + /// polled/spawned/awaited. pub fn new(bot: B, limits: Limits) -> (Self, impl Future) { // FIXME: just a random number, currently let (queue_tx, queue_rx) = mpsc::channel(130); - let worker = worker( - limits, - queue_rx, - ); + let worker = worker(limits, queue_rx); - let this = Self { bot, queue: queue_tx, }; + let this = Self { bot, queue: queue_tx }; (this, worker) } + + /// Creates new [`Throttle`] spawning the worker with `tokio::spawn` + /// + /// Note: it's recommended to use [`RequesterExt::throttle`] instead. + pub fn new_spawn(bot: B, limits: Limits) -> Self + where + // Basically, I hate this bound. + // This is yet another problem caused by [rust-lang/#76882]. + // And I think it *is* a bug. + // + // [rust-lang/#76882]: https://github.com/rust-lang/rust/issues/76882 + // + // Though crucially I can't think of a case with non-static bot. + // But anyway, it doesn't change the fact that this bound is redundant. + // + // (waffle) + B: 'static, + { + let (this, worker) = Self::new(bot, limits); + tokio::spawn(worker); + this + } + + /// Allows to access inner bot + pub fn inner(&self) -> &B { + &self.bot + } + + /// Unwraps inner bot + pub fn into_inner(self) -> B { + self.bot + } } -impl Requester for Limited { +impl Requester for Throttle { type GetMe = B::GetMe; fn get_me(&self) -> Self::GetMe { self.bot.get_me() } - type SendMessage = LimitedRequest; + type SendMessage = ThrottlingRequest; fn send_message(&self, chat_id: C, text: T) -> Self::SendMessage where C: Into, - T: Into + T: Into, { - LimitedRequest(self.bot.send_message(chat_id, text), self.queue.clone()) + ThrottlingRequest(self.bot.send_message(chat_id, text), self.queue.clone()) } } @@ -196,9 +228,9 @@ impl GetChatId for SendMessage { } } -pub struct LimitedRequest(R, mpsc::Sender<(ChatId, Sender<()>)>); +pub struct ThrottlingRequest(R, mpsc::Sender<(ChatId, Sender<()>)>); -impl HasPayload for LimitedRequest { +impl HasPayload for ThrottlingRequest { type Payload = R::Payload; fn payload_mut(&mut self) -> &mut Self::Payload { @@ -210,7 +242,7 @@ impl HasPayload for LimitedRequest { } } -impl Request for LimitedRequest +impl Request for ThrottlingRequest where ::Payload: GetChatId, { @@ -218,14 +250,10 @@ where type Send = LimitedSend; type SendRef = LimitedSend; - fn send(mut self) -> Self::Send { + fn send(self) -> Self::Send { let (tx, rx) = channel(); let send = self.1.send_t((self.0.payload_ref().get_chat_id().clone(), tx)); - LimitedSend::Registering { - request: self.0, - send, - wait: rx, - } + LimitedSend::Registering { request: self.0, send, wait: rx } } fn send_ref(&self) -> Self::SendRef { @@ -263,7 +291,9 @@ impl Future for LimitedSend { Poll::Ready(r) => { // FIXME(waffle): remove unwrap r.unwrap(); - if let SendRepl::Registering { request, send: _, wait } = self.as_mut().project_replace(LimitedSend::Done) { + if let SendRepl::Registering { request, send: _, wait } = + self.as_mut().project_replace(LimitedSend::Done) + { self.as_mut().project_replace(LimitedSend::Pending { request, wait }); } @@ -275,7 +305,9 @@ impl Future for LimitedSend { Poll::Ready(r) => { // FIXME(waffle): remove unwrap r.unwrap(); - if let SendRepl::Pending { request, wait: _ } = self.as_mut().project_replace(LimitedSend::Done) { + if let SendRepl::Pending { request, wait: _ } = + self.as_mut().project_replace(LimitedSend::Done) + { self.as_mut().project_replace(LimitedSend::Sent { fut: request.send() }); } @@ -292,27 +324,19 @@ impl Future for LimitedSend { } } -use chan_send::{ChanSend, SendTy as _}; -use crate::bot::limits::chan_send::SendTy; -use std::collections::hash_map::Entry; -use core::mem; -use vecrem::VecExt; - mod chan_send { - use tokio::sync::mpsc; use crate::types::ChatId; - use tokio::sync::oneshot::Sender; - use std::future::Future; use futures::task::{Context, Poll}; use pin_project::__private::Pin; - use tokio::sync::mpsc::error::SendError; + use std::future::Future; + use tokio::sync::{mpsc, mpsc::error::SendError, oneshot::Sender}; pub(crate) trait SendTy { fn send_t(self, val: (ChatId, Sender<()>)) -> ChanSend; } #[pin_project::pin_project] - pub/*(crate) */struct ChanSend(#[pin] Inner); // FIXME + pub struct ChanSend(#[pin] Inner); // FIXME #[cfg(not(feature = "nightly"))] type Inner = Pin)>>>>>; @@ -323,7 +347,10 @@ mod chan_send { fn send_t(mut self, val: (ChatId, Sender<()>)) -> ChanSend { #[cfg(feature = "nightly")] { - fn def(mut sender: mpsc::Sender<(ChatId, Sender<()>)>, val: (ChatId, Sender<()>)) -> Inner { + fn def( + mut sender: mpsc::Sender<(ChatId, Sender<()>)>, + val: (ChatId, Sender<()>), + ) -> Inner { async move { sender.send(val).await } } return ChanSend(def(self, val)); @@ -340,5 +367,4 @@ mod chan_send { self.project().0.poll(cx) } } - -} \ No newline at end of file +} diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 54154cad..01698ed5 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -21,6 +21,7 @@ mod limits; pub use auto_send::AutoSend; pub use cache_me::CacheMe; +pub use limits::{Limits, Throttle}; pub(crate) const TELOXIDE_TOKEN: &str = "TELOXIDE_TOKEN"; pub(crate) const TELOXIDE_PROXY: &str = "TELOXIDE_PROXY"; diff --git a/src/lib.rs b/src/lib.rs index 214553c8..6e7b78b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,8 +27,11 @@ pub mod prelude; pub mod requests; pub mod types; +// FIXME(waffle): made `pub` to reexport bot wrappers, in future we may want to +// reexport them from elsewhere +pub mod bot; + // reexported -mod bot; mod errors; // implementation details diff --git a/src/payloads/send_message.rs b/src/payloads/send_message.rs index 5d483525..c2ed2e00 100644 --- a/src/payloads/send_message.rs +++ b/src/payloads/send_message.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use crate::{ requests::{HasPayload, Payload}, - types::{ChatId, Message, ParseMode, ReplyMarkup} + types::{ChatId, Message, ParseMode, ReplyMarkup}, }; /// Use this method to send text messages. diff --git a/src/payloads/setters.rs b/src/payloads/setters.rs index 646f1a88..110698ae 100644 --- a/src/payloads/setters.rs +++ b/src/payloads/setters.rs @@ -1,2 +1 @@ -pub use crate::payloads::GetMeSetters as _; -pub use crate::payloads::SendMessageSetters as _; +pub use crate::payloads::{GetMeSetters as _, SendMessageSetters as _}; diff --git a/src/requests/requester.rs b/src/requests/requester.rs index 69dd1d25..f9f342d1 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -1,8 +1,5 @@ use crate::{ - payloads::{ - GetMe, - SendMessage - }, + payloads::{GetMe, SendMessage}, requests::Request, types::ChatId, }; diff --git a/src/requests/requester_ext.rs b/src/requests/requester_ext.rs index 6d92a531..e6370846 100644 --- a/src/requests/requester_ext.rs +++ b/src/requests/requester_ext.rs @@ -1,4 +1,8 @@ -use crate::{requests::Requester, AutoSend, CacheMe}; +use crate::{ + bot::{CacheMe, Limits, Throttle}, + requests::Requester, + AutoSend, +}; pub trait RequesterExt: Requester { /// Add `get_me` caching ability, see [`CacheMe`] for more. @@ -16,6 +20,19 @@ pub trait RequesterExt: Requester { { AutoSend::new(self) } + + /// Add throttling ability, see [`Throttle`] for more. + /// + /// Note: this spawns the worker, just as [`Throttle::new_spawn`]. + fn throttle(self, limits: Limits) -> Throttle + where + Self: Sized, + // >:( + // (waffle) + Self: 'static, + { + Throttle::new_spawn(self, limits) + } } impl RequesterExt for T From 23ef060d0855c029d2fad4f8b8905eacdadbe38b Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 1 Oct 2020 00:15:55 +0300 Subject: [PATCH 114/755] [throttle] temporary resolve `spawn_blocking` fixme --- src/bot/limits.rs | 47 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/bot/limits.rs b/src/bot/limits.rs index 9b74ae72..7c3bfa94 100644 --- a/src/bot/limits.rs +++ b/src/bot/limits.rs @@ -54,19 +54,19 @@ pub struct Throttle { } async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(ChatId, Sender<()>)>) { - // FIXME: use spawn_blocking? - // FIXME: remove unnecessary ChatId clones - // FIXME: Make an research about data structures for this queue. - // Currently this is O(n) removing (n = number of elements stayed), - // amortized O(1) push (vec+vecrem). + // FIXME(waffle): Make an research about data structures for this queue. + // Currently this is O(n) removing (n = number of elements + // stayed), amortized O(1) push (vec+vecrem). let mut queue: Vec<(ChatId, Sender<()>)> = Vec::new(); // FIXME: with_cap + // I wish there was special data structure for history which removed the + // need in 2 hashmaps + // (waffle) let mut history: VecDeque<(ChatId, Instant)> = VecDeque::new(); // hchats[chat] = history.iter().filter(|(c, _)| c == chat).count() let mut hchats: HashMap = HashMap::new(); - let mut hchats_s = HashMap::new(); loop { @@ -78,9 +78,42 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(ChatId, Sender<()> // update local queue with latest requests while let Ok(e) = queue_rx.try_recv() { // FIXME: properly check for errors (stop when the bot's sender is dropped?) - queue.push(e) + queue.push(e); } + // _Maybe_ we need to use `spawn_blocking` here, because there is + // decent amount of blocking work. However _for now_ I've decided not + // to use it here. + // + // Reasons (not to use `spawn_blocking`): + // + // 1. The work seems not very CPU-bound, it's not heavy computations, + // it's more like light computations. + // + // 2. `spawn_blocking` is not zero-cost — it spawns a new system thread + // + do so other work. This may actually be *worse* then current + // "just do everything in this async fn" approach. + // + // 3. With `rt-threaded` feature, tokio uses [`num_cpus()`] threads + // which should be enough to work fine with one a-bit-blocking task. + // Crucially current behaviour will be problem mostly with + // single-threaded runtimes (and in case you're using one, you + // probably don't want to spawn unnecessary threads anyway). + // + // I think if we'll ever change this behaviour, we need to make it + // _configurable_. + // + // See also [discussion (ru)]. + // + // NOTE: If you are reading this because you have any problems because + // of this worker, open an [issue on github] + // + // [`num_cpus()`]: https://vee.gg/JGwq2 + // [discussion (ru)]: https://t.me/rust_async/27891 + // [issue on github]: https://github.com/teloxide/teloxide/issues/new + // + // (waffle) + let now = Instant::now(); let min_back = now - MINUTE; let sec_back = now - SECOND; From 247868a815398174717154a953885ce23ee3ed32 Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 1 Oct 2020 16:51:59 +0300 Subject: [PATCH 115/755] [throttle] explicit queue-closed error handling & comments --- src/bot/limits.rs | 85 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 25 deletions(-) diff --git a/src/bot/limits.rs b/src/bot/limits.rs index 7c3bfa94..b99586b1 100644 --- a/src/bot/limits.rs +++ b/src/bot/limits.rs @@ -8,7 +8,7 @@ use std::{ use futures::task::{Context, Poll}; use tokio::{ sync::{ - mpsc, + mpsc::{self, error::TryRecvError}, oneshot::{channel, Receiver, Sender}, }, time::delay_for, @@ -48,6 +48,23 @@ impl Default for Limits { } } +/// +/// +/// ## Note about send-by-@username +/// +/// Telegram have limits on sending messages to _the same chat_. To check them +/// we store `chat_id`s of several last requests. _However_ there is no good way +/// to tell if given `ChatId::Id(x)` corresponds to the same chat as +/// `ChatId::ChannelUsername(u)`. +/// +/// Our current approach is to just give up and check `chat_id_a == chat_id_b`. +/// This may give incorrect results. +/// +/// Also, current algorithm requires to `clone` `chat_id` several times, which +/// can be quire expensive for strings (though this may be fixed in the future) +/// +/// As such, we encourage not to use `ChatId::ChannelUsername(u)` with this bot +/// wrapper. pub struct Throttle { bot: B, queue: mpsc::Sender<(ChatId, Sender<()>)>, @@ -72,13 +89,22 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(ChatId, Sender<()> loop { // If there are no pending requests we are just waiting if queue.is_empty() { - queue.push(queue_rx.recv().await.unwrap()); + let req = queue_rx + .recv() + .await + // FIXME(waffle): decide what should we do on channel close + .expect("Queue channel was closed"); + queue.push(req); } // update local queue with latest requests - while let Ok(e) = queue_rx.try_recv() { - // FIXME: properly check for errors (stop when the bot's sender is dropped?) - queue.push(e); + loop { + match queue_rx.try_recv() { + Ok(req) => queue.push(req), + Err(TryRecvError::Empty) => break, + // FIXME(waffle): decide what should we do on channel close + Err(TryRecvError::Closed) => unimplemented!("Queue channel was closed"), + } } // _Maybe_ we need to use `spawn_blocking` here, because there is @@ -126,9 +152,11 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(ChatId, Sender<()> } if let Some((chat, _)) = history.pop_front() { - if let Entry::Occupied(entry) = hchats.entry(chat).and_modify(|count| { + let ent = hchats.entry(chat).and_modify(|count| { *count -= 1; - }) { + }); + + if let Entry::Occupied(entry) = ent { if *entry.get() == 0 { entry.remove_entry(); } @@ -138,9 +166,8 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(ChatId, Sender<()> // as truncates which is ok since in case of truncation it would always be >= // limits.overall_s - let mut allowed = limits - .overall_s - .saturating_sub(history.iter().take_while(|(_, time)| time > &sec_back).count() as u32); + let used = history.iter().take_while(|(_, time)| time > &sec_back).count() as u32; + let mut allowed = limits.overall_s.saturating_sub(used); if allowed == 0 { hchats_s.clear(); @@ -166,8 +193,14 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(ChatId, Sender<()> *hchats.entry(chat.clone()).or_insert(0) += 1; history.push_back((chat.clone(), Instant::now())); } - entry.remove().1.send(()); + // Explicitly ignore result. + // + // If request doesn't listen to unlock channel we don't want + // anything to do with it. + let _ = entry.remove().1.send(()); + + // We've "sent" 1 request, so now we can send 1 less allowed -= 1; if allowed == 0 { break; @@ -178,6 +211,8 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(ChatId, Sender<()> } drop(queue_rem); + // It's easier to just recompute last second stats, instead of keeping + // track of it alongside with minute stats, so we just throw this away. hchats_s.clear(); delay_for(DELAY).await; } @@ -251,7 +286,6 @@ impl Requester for Throttle { } pub trait GetChatId { - // FIXME(waffle): add note about false negatives with ChatId::Username fn get_chat_id(&self) -> &ChatId; } @@ -280,13 +314,13 @@ where ::Payload: GetChatId, { type Err = R::Err; - type Send = LimitedSend; - type SendRef = LimitedSend; + type Send = ThrottlingSend; + type SendRef = ThrottlingSend; fn send(self) -> Self::Send { let (tx, rx) = channel(); let send = self.1.send_t((self.0.payload_ref().get_chat_id().clone(), tx)); - LimitedSend::Registering { request: self.0, send, wait: rx } + ThrottlingSend::Registering { request: self.0, send, wait: rx } } fn send_ref(&self) -> Self::SendRef { @@ -295,7 +329,7 @@ where } #[pin_project::pin_project(project = SendProj, project_replace = SendRepl)] -pub enum LimitedSend { +pub enum ThrottlingSend { Registering { request: R, #[pin] @@ -314,7 +348,7 @@ pub enum LimitedSend { Done, } -impl Future for LimitedSend { +impl Future for ThrottlingSend { type Output = Result, R::Err>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -325,9 +359,9 @@ impl Future for LimitedSend { // FIXME(waffle): remove unwrap r.unwrap(); if let SendRepl::Registering { request, send: _, wait } = - self.as_mut().project_replace(LimitedSend::Done) + self.as_mut().project_replace(ThrottlingSend::Done) { - self.as_mut().project_replace(LimitedSend::Pending { request, wait }); + self.as_mut().project_replace(ThrottlingSend::Pending { request, wait }); } self.poll(cx) @@ -339,9 +373,9 @@ impl Future for LimitedSend { // FIXME(waffle): remove unwrap r.unwrap(); if let SendRepl::Pending { request, wait: _ } = - self.as_mut().project_replace(LimitedSend::Done) + self.as_mut().project_replace(ThrottlingSend::Done) { - self.as_mut().project_replace(LimitedSend::Sent { fut: request.send() }); + self.as_mut().project_replace(ThrottlingSend::Sent { fut: request.send() }); } self.poll(cx) @@ -349,7 +383,7 @@ impl Future for LimitedSend { }, SendProj::Sent { fut } => { let res = futures::ready!(fut.poll(cx)); - self.set(LimitedSend::Done); + self.set(ThrottlingSend::Done); Poll::Ready(res) } SendProj::Done => Poll::Pending, @@ -358,12 +392,13 @@ impl Future for LimitedSend { } mod chan_send { - use crate::types::ChatId; + use std::{future::Future, pin::Pin}; + use futures::task::{Context, Poll}; - use pin_project::__private::Pin; - use std::future::Future; use tokio::sync::{mpsc, mpsc::error::SendError, oneshot::Sender}; + use crate::types::ChatId; + pub(crate) trait SendTy { fn send_t(self, val: (ChatId, Sender<()>)) -> ChanSend; } From 7fdf9cd9a63f3bb2c8349ac47679cd3b0970fe75 Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 1 Oct 2020 17:21:30 +0300 Subject: [PATCH 116/755] [throttle] implement send_ref --- Cargo.toml | 2 +- src/bot/limits.rs | 79 +++++++++++++++++++++++++++++++++++++++-- src/requests/request.rs | 11 ++++++ 3 files changed, 89 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 01f3f1ca..a247f0ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ authors = [ [dependencies] futures = "0.3.5" -tokio = { version = "0.2.21", features = ["fs", "stream"] } +tokio = { version = "0.2.21", features = ["fs", "stream", "full"] } tokio-util = "0.3.1" pin-project = "0.4.23" bytes = "0.5.5" diff --git a/src/bot/limits.rs b/src/bot/limits.rs index b99586b1..3cdf0f54 100644 --- a/src/bot/limits.rs +++ b/src/bot/limits.rs @@ -315,7 +315,7 @@ where { type Err = R::Err; type Send = ThrottlingSend; - type SendRef = ThrottlingSend; + type SendRef = ThrottlingSendRef; fn send(self) -> Self::Send { let (tx, rx) = channel(); @@ -324,7 +324,18 @@ where } fn send_ref(&self) -> Self::SendRef { - unimplemented!() + let (tx, rx) = channel(); + let send = self.1.clone().send_t((self.0.payload_ref().get_chat_id().clone(), tx)); + + // As we can't move self.0 (request) out, as we do in `send` we are + // forced to call `send_ref()`. This may have overhead and/or lead to + // wrong results because `R::send_ref` does the send. + // + // However `Request` documentation explicitly notes that `send{,_ref}` + // should **not** do any kind of work, so it's ok. + let request = self.0.send_ref(); + + ThrottlingSendRef::Registering { request, send, wait: rx } } } @@ -391,6 +402,70 @@ impl Future for ThrottlingSend { } } +#[pin_project::pin_project(project = SendRefProj, project_replace = SendRefRepl)] +pub enum ThrottlingSendRef { + Registering { + request: R::SendRef, + #[pin] + send: ChanSend, + wait: Receiver<()>, + }, + Pending { + request: R::SendRef, + #[pin] + wait: Receiver<()>, + }, + Sent { + #[pin] + fut: R::SendRef, + }, + Done, +} + +impl Future for ThrottlingSendRef { + type Output = Result, R::Err>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.as_mut().project() { + SendRefProj::Registering { request: _, send, wait: _ } => match send.poll(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(r) => { + // FIXME(waffle): remove unwrap + r.unwrap(); + if let SendRefRepl::Registering { request, send: _, wait } = + self.as_mut().project_replace(ThrottlingSendRef::Done) + { + self.as_mut().project_replace(ThrottlingSendRef::Pending { request, wait }); + } + + self.poll(cx) + } + }, + SendRefProj::Pending { request: _, wait } => match wait.poll(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(r) => { + // FIXME(waffle): remove unwrap + r.unwrap(); + if let SendRefRepl::Pending { request, wait: _ } = + self.as_mut().project_replace(ThrottlingSendRef::Done) + { + self.as_mut().project_replace(ThrottlingSendRef::Sent { fut: request }); + } + + self.poll(cx) + } + }, + SendRefProj::Sent { fut } => { + let res = futures::ready!(fut.poll(cx)); + self.set(ThrottlingSendRef::Done); + Poll::Ready(res) + } + SendRefProj::Done => Poll::Pending, + } + } +} + + mod chan_send { use std::{future::Future, pin::Pin}; diff --git a/src/requests/request.rs b/src/requests/request.rs index b29a6c00..da2be6fa 100644 --- a/src/requests/request.rs +++ b/src/requests/request.rs @@ -3,7 +3,18 @@ use std::future::Future; use crate::requests::{HasPayload, Output}; /// A ready-to-send telegram request. +/// // FIXME(waffle): Write better doc for the trait +/// +/// ## Implementation notes +/// +/// It is not recommended to do any kind of _work_ in `send` or `send_ref`. +/// Instead it's recommended to do all the (possible) stuff in the returned +/// future. In other words — keep it lazy. +/// +/// This is crucial for request wrappers which may want to cancel and/or never +/// send the underlying request. E.g.: [`Throttle`]'s `send_ref` calls +/// `B::send_ref` while _not_ meaning to really send the request right now. #[cfg_attr(all(docsrs, feature = "nightly"), doc(spotlight))] pub trait Request: HasPayload { /* From 6f03ca0954e275c04517804e2e3ca3283848586d Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 2 Oct 2020 13:28:46 +0300 Subject: [PATCH 117/755] [throttle] hash `ChatId::ChannelUsername` to prevent expensive string cloning & make send{,_ref} enums private The future has come! --- src/bot/limits.rs | 111 ++++++++++++++++++++++++++++++---------------- 1 file changed, 73 insertions(+), 38 deletions(-) diff --git a/src/bot/limits.rs b/src/bot/limits.rs index 3cdf0f54..dc66bcd6 100644 --- a/src/bot/limits.rs +++ b/src/bot/limits.rs @@ -21,6 +21,7 @@ use crate::{ requests::{HasPayload, Output, Request, Requester}, types::ChatId, }; +use std::hash::{Hash, Hasher}; const MINUTE: Duration = Duration::from_secs(50); // FIXME: min = sec * 10 only in tests const SECOND: Duration = Duration::from_secs(1); @@ -60,30 +61,27 @@ impl Default for Limits { /// Our current approach is to just give up and check `chat_id_a == chat_id_b`. /// This may give incorrect results. /// -/// Also, current algorithm requires to `clone` `chat_id` several times, which -/// can be quire expensive for strings (though this may be fixed in the future) -/// /// As such, we encourage not to use `ChatId::ChannelUsername(u)` with this bot /// wrapper. pub struct Throttle { bot: B, - queue: mpsc::Sender<(ChatId, Sender<()>)>, + queue: mpsc::Sender<(Id, Sender<()>)>, } -async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(ChatId, Sender<()>)>) { +async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender<()>)>) { // FIXME: remove unnecessary ChatId clones // FIXME(waffle): Make an research about data structures for this queue. // Currently this is O(n) removing (n = number of elements // stayed), amortized O(1) push (vec+vecrem). - let mut queue: Vec<(ChatId, Sender<()>)> = Vec::new(); // FIXME: with_cap + let mut queue: Vec<(Id, Sender<()>)> = Vec::new(); // FIXME: with_cap // I wish there was special data structure for history which removed the // need in 2 hashmaps // (waffle) - let mut history: VecDeque<(ChatId, Instant)> = VecDeque::new(); + let mut history: VecDeque<(Id, Instant)> = VecDeque::new(); // hchats[chat] = history.iter().filter(|(c, _)| c == chat).count() - let mut hchats: HashMap = HashMap::new(); + let mut hchats: HashMap = HashMap::new(); let mut hchats_s = HashMap::new(); loop { @@ -285,6 +283,30 @@ impl Requester for Throttle { } } +/// Id used in worker. +/// +/// It is used instead of `ChatId` to make copying cheap even in case of +/// usernames. (It just hashes username) +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] +enum Id { + Id(i64), + Ch(u64), +} + +impl From<&ChatId> for Id { + fn from(value: &ChatId) -> Self { + match value { + ChatId::Id(id) => Id::Id(*id), + ChatId::ChannelUsername(username) => { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + username.hash(&mut hasher); + let hash = hasher.finish(); + Id::Ch(hash) + } + } + } +} + pub trait GetChatId { fn get_chat_id(&self) -> &ChatId; } @@ -295,7 +317,7 @@ impl GetChatId for SendMessage { } } -pub struct ThrottlingRequest(R, mpsc::Sender<(ChatId, Sender<()>)>); +pub struct ThrottlingRequest(R, mpsc::Sender<(Id, Sender<()>)>); impl HasPayload for ThrottlingRequest { type Payload = R::Payload; @@ -319,13 +341,13 @@ where fn send(self) -> Self::Send { let (tx, rx) = channel(); - let send = self.1.send_t((self.0.payload_ref().get_chat_id().clone(), tx)); - ThrottlingSend::Registering { request: self.0, send, wait: rx } + let send = self.1.send_t((self.0.payload_ref().get_chat_id().into(), tx)); + ThrottlingSend(ThrottlingSendInner::Registering { request: self.0, send, wait: rx }) } fn send_ref(&self) -> Self::SendRef { let (tx, rx) = channel(); - let send = self.1.clone().send_t((self.0.payload_ref().get_chat_id().clone(), tx)); + let send = self.1.clone().send_t((self.0.payload_ref().get_chat_id().into(), tx)); // As we can't move self.0 (request) out, as we do in `send` we are // forced to call `send_ref()`. This may have overhead and/or lead to @@ -335,12 +357,15 @@ where // should **not** do any kind of work, so it's ok. let request = self.0.send_ref(); - ThrottlingSendRef::Registering { request, send, wait: rx } + ThrottlingSendRef(ThrottlingSendRefInner::Registering { request, send, wait: rx }) } } +#[pin_project::pin_project] +pub struct ThrottlingSend(#[pin] ThrottlingSendInner); + #[pin_project::pin_project(project = SendProj, project_replace = SendRepl)] -pub enum ThrottlingSend { +enum ThrottlingSendInner { Registering { request: R, #[pin] @@ -363,16 +388,18 @@ impl Future for ThrottlingSend { type Output = Result, R::Err>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.as_mut().project() { + let mut this = self.as_mut().project().0; + + match this.as_mut().project() { SendProj::Registering { request: _, send, wait: _ } => match send.poll(cx) { Poll::Pending => Poll::Pending, Poll::Ready(r) => { // FIXME(waffle): remove unwrap r.unwrap(); if let SendRepl::Registering { request, send: _, wait } = - self.as_mut().project_replace(ThrottlingSend::Done) + this.as_mut().project_replace(ThrottlingSendInner::Done) { - self.as_mut().project_replace(ThrottlingSend::Pending { request, wait }); + this.as_mut().project_replace(ThrottlingSendInner::Pending { request, wait }); } self.poll(cx) @@ -384,9 +411,9 @@ impl Future for ThrottlingSend { // FIXME(waffle): remove unwrap r.unwrap(); if let SendRepl::Pending { request, wait: _ } = - self.as_mut().project_replace(ThrottlingSend::Done) + this.as_mut().project_replace(ThrottlingSendInner::Done) { - self.as_mut().project_replace(ThrottlingSend::Sent { fut: request.send() }); + this.as_mut().project_replace(ThrottlingSendInner::Sent { fut: request.send() }); } self.poll(cx) @@ -394,7 +421,7 @@ impl Future for ThrottlingSend { }, SendProj::Sent { fut } => { let res = futures::ready!(fut.poll(cx)); - self.set(ThrottlingSend::Done); + this.set(ThrottlingSendInner::Done); Poll::Ready(res) } SendProj::Done => Poll::Pending, @@ -402,8 +429,11 @@ impl Future for ThrottlingSend { } } +#[pin_project::pin_project] +pub struct ThrottlingSendRef(#[pin] ThrottlingSendRefInner); + #[pin_project::pin_project(project = SendRefProj, project_replace = SendRefRepl)] -pub enum ThrottlingSendRef { +enum ThrottlingSendRefInner { Registering { request: R::SendRef, #[pin] @@ -426,16 +456,18 @@ impl Future for ThrottlingSendRef { type Output = Result, R::Err>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.as_mut().project() { + let mut this = self.as_mut().project().0; + + match this.as_mut().project() { SendRefProj::Registering { request: _, send, wait: _ } => match send.poll(cx) { Poll::Pending => Poll::Pending, Poll::Ready(r) => { // FIXME(waffle): remove unwrap r.unwrap(); if let SendRefRepl::Registering { request, send: _, wait } = - self.as_mut().project_replace(ThrottlingSendRef::Done) + this.as_mut().project_replace(ThrottlingSendRefInner::Done) { - self.as_mut().project_replace(ThrottlingSendRef::Pending { request, wait }); + this.as_mut().project_replace(ThrottlingSendRefInner::Pending { request, wait }); } self.poll(cx) @@ -447,9 +479,9 @@ impl Future for ThrottlingSendRef { // FIXME(waffle): remove unwrap r.unwrap(); if let SendRefRepl::Pending { request, wait: _ } = - self.as_mut().project_replace(ThrottlingSendRef::Done) + this.as_mut().project_replace(ThrottlingSendRefInner::Done) { - self.as_mut().project_replace(ThrottlingSendRef::Sent { fut: request }); + this.as_mut().project_replace(ThrottlingSendRefInner::Sent { fut: request }); } self.poll(cx) @@ -457,7 +489,7 @@ impl Future for ThrottlingSendRef { }, SendRefProj::Sent { fut } => { let res = futures::ready!(fut.poll(cx)); - self.set(ThrottlingSendRef::Done); + this.set(ThrottlingSendRefInner::Done); Poll::Ready(res) } SendRefProj::Done => Poll::Pending, @@ -472,27 +504,30 @@ mod chan_send { use futures::task::{Context, Poll}; use tokio::sync::{mpsc, mpsc::error::SendError, oneshot::Sender}; - use crate::types::ChatId; + use crate::{ + types::ChatId, + bot::limits::Id + }; - pub(crate) trait SendTy { - fn send_t(self, val: (ChatId, Sender<()>)) -> ChanSend; + pub(super) trait SendTy { + fn send_t(self, val: (Id, Sender<()>)) -> ChanSend; } #[pin_project::pin_project] - pub struct ChanSend(#[pin] Inner); // FIXME + pub(super) struct ChanSend(#[pin] Inner); // FIXME #[cfg(not(feature = "nightly"))] - type Inner = Pin)>>>>>; + type Inner = Pin)>>>>>; #[cfg(feature = "nightly")] - type Inner = impl Future)>>>; + type Inner = impl Future)>>>; - impl SendTy for mpsc::Sender<(ChatId, Sender<()>)> { - fn send_t(mut self, val: (ChatId, Sender<()>)) -> ChanSend { + impl SendTy for mpsc::Sender<(Id, Sender<()>)> { + fn send_t(mut self, val: (Id, Sender<()>)) -> ChanSend { #[cfg(feature = "nightly")] { fn def( - mut sender: mpsc::Sender<(ChatId, Sender<()>)>, - val: (ChatId, Sender<()>), + mut sender: mpsc::Sender<(Id, Sender<()>)>, + val: (Id, Sender<()>), ) -> Inner { async move { sender.send(val).await } } @@ -504,7 +539,7 @@ mod chan_send { } impl Future for ChanSend { - type Output = Result<(), SendError<(ChatId, Sender<()>)>>; + type Output = Result<(), SendError<(Id, Sender<()>)>>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.project().0.poll(cx) From 380e189ec7fef772cdd654c0e27c18745d22efc9 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 2 Oct 2020 16:23:49 +0300 Subject: [PATCH 118/755] [throttle] comment and doc improvements --- src/bot/limits.rs | 92 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 81 insertions(+), 11 deletions(-) diff --git a/src/bot/limits.rs b/src/bot/limits.rs index dc66bcd6..2b311110 100644 --- a/src/bot/limits.rs +++ b/src/bot/limits.rs @@ -23,13 +23,59 @@ use crate::{ }; use std::hash::{Hash, Hasher}; -const MINUTE: Duration = Duration::from_secs(50); // FIXME: min = sec * 10 only in tests +// Throttling is quite complicated this comment describes the algorithm of current implementation. +// NOTE: this only describes CURRENT implementation. Implementation may change at any time. +// +// ### Request +// +// When throttling request is sent, it sends a tuple of `ChatId` (more accurately, just local `Id`) +// and `Sender<()>` to the worker. Then the request waits for notification from worker. When +// notification is received it sends underlying request. +// +// ### Worker +// +// Worker does the most important job - it checks for limit exceed. +// +// The worker stores "history" of requests sent in last minute (and to which chats the were sent) +// and queue of pending updates. +// +// The worker does the following algorithm loop: +// +// 1. If queue is empty wait for the first message in incoming channel (and adds it to queue). +// +// 2. Read all present messages from incoming channel and transfer them to queue. +// +// 3. Record current time. +// +// 4. Clear history from records which time < (current - minute) +// +// 5. Count all requests in which were sent last second, `allowed = limit.overall_s - count` +// +// 6. If `allowed == 0` wait a bit and `continue` to the next iteration +// +// 7. Count how many requests were sent to which chats (i.e.: create `Map`) +// (note: the same map, but for last minute also exists, but it's updated, instead of recreation) +// +// 8. While `allowed >= 0` search for requests which chat hasn't exceed limits +// (i.e.: map[chat] < limit), if one is found, decrease `allowed`, notify request +// that it can be now executed, increase counts, add record to history. + +const MINUTE: Duration = Duration::from_secs(60); const SECOND: Duration = Duration::from_secs(1); -const DELAY: Duration = Duration::from_millis(250); // second/4 + +// Delay between worker iterations. +// +// For now it's `second/4`, but that number is chosen pretty randomly, we may want to change this. +const DELAY: Duration = Duration::from_millis(250); /// Telegram request limits. /// -/// This struct is used in [`Throttle`] +/// This struct is used in [`Throttle`]. +/// +/// Note that you may ask telegram [@BotSupport] to increase limits for your +/// particular bot if it has a lot of users (but they may or may not do that). +/// +/// [@BotSupport]: https://t.me/botsupport #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct Limits { /// Allowed messages in one chat per second @@ -49,9 +95,32 @@ impl Default for Limits { } } -/// +/// Automatic request limits respecting mechanism. /// -/// ## Note about send-by-@username +/// Telegram has strict [limits], which, if exceeded will sooner or later cause +/// `RequestError::RetryAfter(_)` errors. These errors can cause users of your +/// bot to never receive responds from the bot or receive them in wrong order. +/// +/// This bot wrapper automatically checks for limits, suspending requests until +/// they could be sent without exceeding limits (request order in chats is not changed). +/// +/// It's recommended to use this wrapper before other wrappers (i.e.: `SomeWrapper>`) +/// because if done otherwise inner wrappers may cause `Throttle` to miscalculate limits usage. +/// +/// [limits]: https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this +/// +/// ## Examples +/// +/// ``` +/// use teloxide_core::{bot::Limits, Bot, requests::RequesterExt}; +/// +/// let bot = Bot::new("TOKEN") +/// .throttle(Limits::default()); +/// +/// /* send many requests here */ +/// ``` +/// +/// ## Note about send-by-@channelusername /// /// Telegram have limits on sending messages to _the same chat_. To check them /// we store `chat_id`s of several last requests. _However_ there is no good way @@ -69,8 +138,6 @@ pub struct Throttle { } async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender<()>)>) { - // FIXME: remove unnecessary ChatId clones - // FIXME(waffle): Make an research about data structures for this queue. // Currently this is O(n) removing (n = number of elements // stayed), amortized O(1) push (vec+vecrem). @@ -222,11 +289,14 @@ impl Throttle { /// Note: [`Throttle`] will only send requests if returned worker is /// polled/spawned/awaited. pub fn new(bot: B, limits: Limits) -> (Self, impl Future) { - // FIXME: just a random number, currently - let (queue_tx, queue_rx) = mpsc::channel(130); + // A buffer made slightly bigger (112.5%) than overall limit + // so we won't lose performance when hitting limits. + // + // (I hope this makes sense) (waffle) + let buffer = limits.overall_s + (limits.overall_s / 8); + let (queue_tx, queue_rx) = mpsc::channel(buffer as usize); let worker = worker(limits, queue_rx); - let this = Self { bot, queue: queue_tx }; (this, worker) @@ -514,7 +584,7 @@ mod chan_send { } #[pin_project::pin_project] - pub(super) struct ChanSend(#[pin] Inner); // FIXME + pub(super) struct ChanSend(#[pin] Inner); #[cfg(not(feature = "nightly"))] type Inner = Pin)>>>>>; From d43f2514d3544af11000e8cc44d118dc1880f3d7 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 2 Oct 2020 18:37:15 +0300 Subject: [PATCH 119/755] [throttle] use channel close to send unlock "messages" Also use `with_capacity` for queue --- Cargo.toml | 3 +- src/bot/limits.rs | 157 ++++++++++++++++++++++++---------------- src/requests/request.rs | 1 - 3 files changed, 97 insertions(+), 64 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a247f0ad..e25be772 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ authors = [ [dependencies] futures = "0.3.5" -tokio = { version = "0.2.21", features = ["fs", "stream", "full"] } +tokio = { version = "0.2.21", features = ["fs", "stream"] } tokio-util = "0.3.1" pin-project = "0.4.23" bytes = "0.5.5" @@ -33,6 +33,7 @@ derive_more = "0.99.9" mime = "0.3.16" thiserror = "1.0.20" once_cell = "1.4.0" +never = "0.1.0" # FIXME(waffle): use crates.io once published vecrem = { git = "https://github.com/WaffleLapkin/vecrem", rev = "6b9b6f42342df8b75548c6ed387072ff235429b1" } diff --git a/src/bot/limits.rs b/src/bot/limits.rs index 2b311110..c160aa2c 100644 --- a/src/bot/limits.rs +++ b/src/bot/limits.rs @@ -1,11 +1,13 @@ use std::{ collections::{hash_map::Entry, HashMap, VecDeque}, future::Future, + hash::{Hash, Hasher}, pin::Pin, time::{Duration, Instant}, }; use futures::task::{Context, Poll}; +use never::Never; use tokio::{ sync::{ mpsc::{self, error::TryRecvError}, @@ -21,51 +23,58 @@ use crate::{ requests::{HasPayload, Output, Request, Requester}, types::ChatId, }; -use std::hash::{Hash, Hasher}; -// Throttling is quite complicated this comment describes the algorithm of current implementation. -// NOTE: this only describes CURRENT implementation. Implementation may change at any time. +// Throttling is quite complicated this comment describes the algorithm of +// current implementation. NOTE: this only describes CURRENT implementation. +// Implementation may change at any time. // // ### Request // -// When throttling request is sent, it sends a tuple of `ChatId` (more accurately, just local `Id`) -// and `Sender<()>` to the worker. Then the request waits for notification from worker. When -// notification is received it sends underlying request. +// When throttling request is sent, it sends a tuple of `ChatId` (more +// accurately, just local `Id`) and `Sender<()>` to the worker. Then the request +// waits for notification from worker. When notification is received it sends +// underlying request. // // ### Worker // // Worker does the most important job - it checks for limit exceed. // -// The worker stores "history" of requests sent in last minute (and to which chats the were sent) -// and queue of pending updates. +// The worker stores "history" of requests sent in last minute (and to which +// chats the were sent) and queue of pending updates. // // The worker does the following algorithm loop: // -// 1. If queue is empty wait for the first message in incoming channel (and adds it to queue). +// 1. If queue is empty wait for the first message in incoming channel (and adds +// it to queue). // -// 2. Read all present messages from incoming channel and transfer them to queue. +// 2. Read all present messages from incoming channel and transfer them to +// queue. // // 3. Record current time. // // 4. Clear history from records which time < (current - minute) // -// 5. Count all requests in which were sent last second, `allowed = limit.overall_s - count` +// 5. Count all requests in which were sent last second, +// `allowed = limit.overall_s - count` // // 6. If `allowed == 0` wait a bit and `continue` to the next iteration // -// 7. Count how many requests were sent to which chats (i.e.: create `Map`) -// (note: the same map, but for last minute also exists, but it's updated, instead of recreation) +// 7. Count how many requests were sent to which chats (i.e.: create +// `Map`) (note: the same map, but for last minute also +// exists, but it's updated, instead of recreation) // // 8. While `allowed >= 0` search for requests which chat hasn't exceed limits -// (i.e.: map[chat] < limit), if one is found, decrease `allowed`, notify request -// that it can be now executed, increase counts, add record to history. +// (i.e.: map[chat] < limit), if one is found, decrease `allowed`, notify +// request that it can be now executed, increase counts, add record to +// history. const MINUTE: Duration = Duration::from_secs(60); const SECOND: Duration = Duration::from_secs(1); // Delay between worker iterations. // -// For now it's `second/4`, but that number is chosen pretty randomly, we may want to change this. +// For now it's `second/4`, but that number is chosen pretty randomly, we may +// want to change this. const DELAY: Duration = Duration::from_millis(250); /// Telegram request limits. @@ -102,20 +111,21 @@ impl Default for Limits { /// bot to never receive responds from the bot or receive them in wrong order. /// /// This bot wrapper automatically checks for limits, suspending requests until -/// they could be sent without exceeding limits (request order in chats is not changed). +/// they could be sent without exceeding limits (request order in chats is not +/// changed). /// -/// It's recommended to use this wrapper before other wrappers (i.e.: `SomeWrapper>`) -/// because if done otherwise inner wrappers may cause `Throttle` to miscalculate limits usage. +/// It's recommended to use this wrapper before other wrappers (i.e.: +/// `SomeWrapper>`) because if done otherwise inner wrappers may +/// cause `Throttle` to miscalculate limits usage. /// /// [limits]: https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this /// /// ## Examples /// /// ``` -/// use teloxide_core::{bot::Limits, Bot, requests::RequesterExt}; +/// use teloxide_core::{bot::Limits, requests::RequesterExt, Bot}; /// -/// let bot = Bot::new("TOKEN") -/// .throttle(Limits::default()); +/// let bot = Bot::new("TOKEN").throttle(Limits::default()); /// /// /* send many requests here */ /// ``` @@ -134,14 +144,17 @@ impl Default for Limits { /// wrapper. pub struct Throttle { bot: B, - queue: mpsc::Sender<(Id, Sender<()>)>, + // Sender is used to pass the signal to unlock by closing the channel. + queue: mpsc::Sender<(Id, Sender)>, } -async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender<()>)>) { +async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender)>) { + // +- Same idea as in `Throttle::new` + let cap = limits.overall_s + (limits.overall_s / 4); // FIXME(waffle): Make an research about data structures for this queue. // Currently this is O(n) removing (n = number of elements // stayed), amortized O(1) push (vec+vecrem). - let mut queue: Vec<(Id, Sender<()>)> = Vec::new(); // FIXME: with_cap + let mut queue: Vec<(Id, Sender)> = Vec::with_capacity(cap as usize); // I wish there was special data structure for history which removed the // need in 2 hashmaps @@ -259,11 +272,8 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender<()>)>) history.push_back((chat.clone(), Instant::now())); } - // Explicitly ignore result. - // - // If request doesn't listen to unlock channel we don't want - // anything to do with it. - let _ = entry.remove().1.send(()); + // This will close the channel unlocking associated request + drop(entry.remove()); // We've "sent" 1 request, so now we can send 1 less allowed -= 1; @@ -387,7 +397,7 @@ impl GetChatId for SendMessage { } } -pub struct ThrottlingRequest(R, mpsc::Sender<(Id, Sender<()>)>); +pub struct ThrottlingRequest(R, mpsc::Sender<(Id, Sender)>); impl HasPayload for ThrottlingRequest { type Payload = R::Payload; @@ -440,12 +450,12 @@ enum ThrottlingSendInner { request: R, #[pin] send: ChanSend, - wait: Receiver<()>, + wait: Receiver, }, Pending { request: R, #[pin] - wait: Receiver<()>, + wait: Receiver, }, Sent { #[pin] @@ -463,13 +473,21 @@ impl Future for ThrottlingSend { match this.as_mut().project() { SendProj::Registering { request: _, send, wait: _ } => match send.poll(cx) { Poll::Pending => Poll::Pending, - Poll::Ready(r) => { - // FIXME(waffle): remove unwrap - r.unwrap(); + Poll::Ready(res) => { if let SendRepl::Registering { request, send: _, wait } = this.as_mut().project_replace(ThrottlingSendInner::Done) { - this.as_mut().project_replace(ThrottlingSendInner::Pending { request, wait }); + match res { + Ok(()) => this + .as_mut() + .project_replace(ThrottlingSendInner::Pending { request, wait }), + // The worker is unlikely to drop queue before sending all requests, + // but just in case it has dropped the queue, we want to just send the + // request. + Err(_) => this + .as_mut() + .project_replace(ThrottlingSendInner::Sent { fut: request.send() }), + }; } self.poll(cx) @@ -478,12 +496,17 @@ impl Future for ThrottlingSend { SendProj::Pending { request: _, wait } => match wait.poll(cx) { Poll::Pending => Poll::Pending, Poll::Ready(r) => { - // FIXME(waffle): remove unwrap - r.unwrap(); + // Worker pass "message" to unlock us by closing the channel + match r { + Ok(never) => match never {}, + Err(_) => {} + } + if let SendRepl::Pending { request, wait: _ } = this.as_mut().project_replace(ThrottlingSendInner::Done) { - this.as_mut().project_replace(ThrottlingSendInner::Sent { fut: request.send() }); + this.as_mut() + .project_replace(ThrottlingSendInner::Sent { fut: request.send() }); } self.poll(cx) @@ -508,12 +531,12 @@ enum ThrottlingSendRefInner { request: R::SendRef, #[pin] send: ChanSend, - wait: Receiver<()>, + wait: Receiver, }, Pending { request: R::SendRef, #[pin] - wait: Receiver<()>, + wait: Receiver, }, Sent { #[pin] @@ -531,13 +554,21 @@ impl Future for ThrottlingSendRef { match this.as_mut().project() { SendRefProj::Registering { request: _, send, wait: _ } => match send.poll(cx) { Poll::Pending => Poll::Pending, - Poll::Ready(r) => { - // FIXME(waffle): remove unwrap - r.unwrap(); + Poll::Ready(res) => { if let SendRefRepl::Registering { request, send: _, wait } = this.as_mut().project_replace(ThrottlingSendRefInner::Done) { - this.as_mut().project_replace(ThrottlingSendRefInner::Pending { request, wait }); + match res { + Ok(()) => this + .as_mut() + .project_replace(ThrottlingSendRefInner::Pending { request, wait }), + // The worker is unlikely to drop queue before sending all requests, + // but just in case it has dropped the queue, we want to just send the + // request. + Err(_) => this + .as_mut() + .project_replace(ThrottlingSendRefInner::Sent { fut: request }), + }; } self.poll(cx) @@ -546,12 +577,17 @@ impl Future for ThrottlingSendRef { SendRefProj::Pending { request: _, wait } => match wait.poll(cx) { Poll::Pending => Poll::Pending, Poll::Ready(r) => { - // FIXME(waffle): remove unwrap - r.unwrap(); + // Worker pass "message" to unlock us by closing the channel + match r { + Ok(never) => match never {}, + Err(_) => {} + } + if let SendRefRepl::Pending { request, wait: _ } = this.as_mut().project_replace(ThrottlingSendRefInner::Done) { - this.as_mut().project_replace(ThrottlingSendRefInner::Sent { fut: request }); + this.as_mut() + .project_replace(ThrottlingSendRefInner::Sent { fut: request }); } self.poll(cx) @@ -567,37 +603,34 @@ impl Future for ThrottlingSendRef { } } - mod chan_send { use std::{future::Future, pin::Pin}; use futures::task::{Context, Poll}; + use never::Never; use tokio::sync::{mpsc, mpsc::error::SendError, oneshot::Sender}; - use crate::{ - types::ChatId, - bot::limits::Id - }; + use crate::bot::limits::Id; pub(super) trait SendTy { - fn send_t(self, val: (Id, Sender<()>)) -> ChanSend; + fn send_t(self, val: (Id, Sender)) -> ChanSend; } #[pin_project::pin_project] pub(super) struct ChanSend(#[pin] Inner); #[cfg(not(feature = "nightly"))] - type Inner = Pin)>>>>>; + type Inner = Pin)>>>>>; #[cfg(feature = "nightly")] - type Inner = impl Future)>>>; + type Inner = impl Future)>>>; - impl SendTy for mpsc::Sender<(Id, Sender<()>)> { - fn send_t(mut self, val: (Id, Sender<()>)) -> ChanSend { + impl SendTy for mpsc::Sender<(Id, Sender)> { + fn send_t(mut self, val: (Id, Sender)) -> ChanSend { #[cfg(feature = "nightly")] { fn def( - mut sender: mpsc::Sender<(Id, Sender<()>)>, - val: (Id, Sender<()>), + mut sender: mpsc::Sender<(Id, Sender)>, + val: (Id, Sender), ) -> Inner { async move { sender.send(val).await } } @@ -609,7 +642,7 @@ mod chan_send { } impl Future for ChanSend { - type Output = Result<(), SendError<(Id, Sender<()>)>>; + type Output = Result<(), SendError<(Id, Sender)>>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.project().0.poll(cx) diff --git a/src/requests/request.rs b/src/requests/request.rs index da2be6fa..8722a3e2 100644 --- a/src/requests/request.rs +++ b/src/requests/request.rs @@ -3,7 +3,6 @@ use std::future::Future; use crate::requests::{HasPayload, Output}; /// A ready-to-send telegram request. -/// // FIXME(waffle): Write better doc for the trait /// /// ## Implementation notes From 0aecda634864f03dcd272bf9261c871c334f3ace Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 2 Oct 2020 20:47:30 +0300 Subject: [PATCH 120/755] [throttle] stop worker when channel is closed --- src/bot/limits.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/bot/limits.rs b/src/bot/limits.rs index c160aa2c..88bbadd1 100644 --- a/src/bot/limits.rs +++ b/src/bot/limits.rs @@ -164,15 +164,16 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender) let mut hchats: HashMap = HashMap::new(); let mut hchats_s = HashMap::new(); - loop { + // set to true when `queue_rx` is closed + let mut close = false; + + while !close || !queue.is_empty() { // If there are no pending requests we are just waiting if queue.is_empty() { - let req = queue_rx - .recv() - .await - // FIXME(waffle): decide what should we do on channel close - .expect("Queue channel was closed"); - queue.push(req); + match queue_rx.recv().await { + Some(req) => queue.push(req), + None => close = true, + } } // update local queue with latest requests @@ -180,8 +181,7 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender) match queue_rx.try_recv() { Ok(req) => queue.push(req), Err(TryRecvError::Empty) => break, - // FIXME(waffle): decide what should we do on channel close - Err(TryRecvError::Closed) => unimplemented!("Queue channel was closed"), + Err(TryRecvError::Closed) => close = true, } } From e7579991359bd0b50899e922de7b043d1302fbe5 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 2 Oct 2020 21:22:19 +0300 Subject: [PATCH 121/755] [throttle] git rebase, fmt and clippy things --- src/bot/auto_send.rs | 15 ++++++++++++++- src/bot/limits.rs | 41 +++++++++++++++++++++-------------------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/bot/auto_send.rs b/src/bot/auto_send.rs index e64b893c..5f2ceb49 100644 --- a/src/bot/auto_send.rs +++ b/src/bot/auto_send.rs @@ -6,7 +6,10 @@ use std::{ use futures::future::FusedFuture; -use crate::requests::{HasPayload, Output, Request, Requester}; +use crate::{ + requests::{HasPayload, Output, Request, Requester}, + types::ChatId, +}; /// Send requests automatically. /// @@ -65,6 +68,16 @@ impl Requester for AutoSend { fn get_me(&self) -> Self::GetMe { AutoRequest::new(self.bot.get_me()) } + + type SendMessage = AutoRequest; + + fn send_message(&self, chat_id: C, text: T) -> Self::SendMessage + where + C: Into, + T: Into, + { + AutoRequest::new(self.bot.send_message(chat_id, text)) + } } #[pin_project::pin_project] diff --git a/src/bot/limits.rs b/src/bot/limits.rs index 88bbadd1..71990a53 100644 --- a/src/bot/limits.rs +++ b/src/bot/limits.rs @@ -254,7 +254,7 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender) } for (chat, _) in history.iter().take_while(|(_, time)| time > &sec_back) { - *hchats_s.entry(chat.clone()).or_insert(0) += 1; + *hchats_s.entry(*chat).or_insert(0) += 1; } let mut queue_rem = queue.removing(); @@ -267,9 +267,9 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender) if cond { { - *hchats_s.entry(chat.clone()).or_insert(0) += 1; - *hchats.entry(chat.clone()).or_insert(0) += 1; - history.push_back((chat.clone(), Instant::now())); + *hchats_s.entry(*chat).or_insert(0) += 1; + *hchats.entry(*chat).or_insert(0) += 1; + history.push_back((*chat, Instant::now())); } // This will close the channel unlocking associated request @@ -495,13 +495,11 @@ impl Future for ThrottlingSend { }, SendProj::Pending { request: _, wait } => match wait.poll(cx) { Poll::Pending => Poll::Pending, - Poll::Ready(r) => { - // Worker pass "message" to unlock us by closing the channel - match r { - Ok(never) => match never {}, - Err(_) => {} - } - + // Worker pass "message" to unlock us by closing the channel, + // and thus we can safely ignore this result as we know it will + // always be `Err(_)` (because `Ok(Never)` is uninhibited) + // and that's what we want. + Poll::Ready(_) => { if let SendRepl::Pending { request, wait: _ } = this.as_mut().project_replace(ThrottlingSendInner::Done) { @@ -576,13 +574,11 @@ impl Future for ThrottlingSendRef { }, SendRefProj::Pending { request: _, wait } => match wait.poll(cx) { Poll::Pending => Poll::Pending, - Poll::Ready(r) => { - // Worker pass "message" to unlock us by closing the channel - match r { - Ok(never) => match never {}, - Err(_) => {} - } - + // Worker pass "message" to unlock us by closing the channel, + // and thus we can safely ignore this result as we know it will + // always be `Err(_)` (because `Ok(Never)` is uninhibited) + // and that's what we want. + Poll::Ready(_) => { if let SendRefRepl::Pending { request, wait: _ } = this.as_mut().project_replace(ThrottlingSendRefInner::Done) { @@ -625,7 +621,9 @@ mod chan_send { type Inner = impl Future)>>>; impl SendTy for mpsc::Sender<(Id, Sender)> { - fn send_t(mut self, val: (Id, Sender)) -> ChanSend { + // `return`s trick IDEA not to show errors + #[allow(clippy::needless_return)] + fn send_t(self, val: (Id, Sender)) -> ChanSend { #[cfg(feature = "nightly")] { fn def( @@ -637,7 +635,10 @@ mod chan_send { return ChanSend(def(self, val)); } #[cfg(not(feature = "nightly"))] - return ChanSend(Box::pin(async move { self.send(val).await })); + { + let mut this = self; + return ChanSend(Box::pin(async move { this.send(val).await })); + } } } From 2d7e40a4732cdd9c7a6d03fb6920707427556d26 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 3 Oct 2020 03:30:34 +0300 Subject: [PATCH 122/755] [throttle] fix doc test --- src/bot/limits.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bot/limits.rs b/src/bot/limits.rs index 71990a53..a5052431 100644 --- a/src/bot/limits.rs +++ b/src/bot/limits.rs @@ -122,9 +122,10 @@ impl Default for Limits { /// /// ## Examples /// -/// ``` +/// ```no_run (throttle fails to spawn task without tokio runtime) /// use teloxide_core::{bot::Limits, requests::RequesterExt, Bot}; /// +/// # #[allow(deprecated)] /// let bot = Bot::new("TOKEN").throttle(Limits::default()); /// /// /* send many requests here */ From 8e44a0bcfcb364a32c7d3eaf2548d8b9b3b7b989 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 3 Oct 2020 03:39:50 +0300 Subject: [PATCH 123/755] fmt: i can't see the difference --- src/bot/limits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bot/limits.rs b/src/bot/limits.rs index a5052431..52f06d44 100644 --- a/src/bot/limits.rs +++ b/src/bot/limits.rs @@ -122,7 +122,7 @@ impl Default for Limits { /// /// ## Examples /// -/// ```no_run (throttle fails to spawn task without tokio runtime) +/// ```no_run (throttle fails to spawn task without tokio runtime) /// use teloxide_core::{bot::Limits, requests::RequesterExt, Bot}; /// /// # #[allow(deprecated)] From 2f09e9930df0268591eb4d4c28e9dc1057659d36 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 10 Oct 2020 14:17:52 +0300 Subject: [PATCH 124/755] Decompose `ChatMemberKind` as was requested per review --- src/types/chat_member.rs | 325 ++++++++++++++++++++++----------------- 1 file changed, 185 insertions(+), 140 deletions(-) diff --git a/src/types/chat_member.rs b/src/types/chat_member.rs index 746c744b..4d7c9530 100644 --- a/src/types/chat_member.rs +++ b/src/types/chat_member.rs @@ -19,255 +19,300 @@ pub struct ChatMember { #[serde(rename_all = "snake_case")] #[serde(tag = "status")] pub enum ChatMemberKind { - Creator { - /// Custom title for this user. - custom_title: Option, - }, - Administrator { - /// Custom title for this user. - custom_title: Option, - - /// `true`, if the bot is allowed to edit - /// administrator privileges of that user. - can_be_edited: bool, - - /// `true`, if the administrator can change the chat - /// title, photo and other settings. - can_change_info: bool, - - /// `true`, if the administrator can post in the - /// channel, channels only. - can_post_messages: Option, - - /// `true`, if the administrator can edit messages of - /// other users and can pin messages, channels only. - can_edit_messages: Option, - - /// `true`, if the administrator can delete messages - /// of other users. - can_delete_messages: bool, - - /// `true`, if the administrator can invite new users - /// to the chat. - can_invite_users: bool, - - /// `true`, if the administrator can restrict, - /// ban or unban chat members. - can_restrict_members: bool, - - /// `true`, if the administrator can pin messages, - /// supergroups only. - can_pin_messages: Option, - - /// `true`, if the administrator can add new - /// administrators with a subset of his own privileges or demote - /// administrators that he has promoted, directly or indirectly - /// (promoted by administrators that were appointed by the - /// user). - can_promote_members: bool, - }, + Creator(Creator), + Administrator(Administrator), Member, - Restricted { - /// Date when restrictions will be lifted for - /// this user, unix time. - until_date: i32, - - /// Restricted only. `true`, if the user can send text messages, - /// contacts, locations and venues. - can_send_messages: bool, - - /// Restricted only. `true`, if the user is allowed to send audios, - /// documents, photos, videos, video notes and voice notes. - can_send_media_messages: bool, - - /// Restricted only. `true`, if the user is allowed to send animations, - /// games, stickers and use inline bots. - can_send_other_messages: bool, - - /// Restricted only. `true`, if the user is allowed to add web page - /// previews to their messages. - can_add_web_page_previews: bool, - }, + Restricted(Restricted), Left, - Kicked { - /// Date when restrictions will be lifted for - /// this user, unix time. - until_date: i32, - }, + Kicked(Kicked), +} + +/// Creator of the group. This struct is part of the [`ChatmemberKind`] enum. +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct Creator { + /// Custom title for this user. + pub custom_title: Option, +} + +/// Administrator of the group. This struct is part of the [`ChatmemberKind`] +/// enum. +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct Administrator { + /// Custom title for this user. + pub custom_title: Option, + + /// `true`, if the bot is allowed to edit + /// administrator privileges of that user. + pub can_be_edited: bool, + + /// `true`, if the administrator can change the chat + /// title, photo and other settings. + pub can_change_info: bool, + + /// `true`, if the administrator can post in the + /// channel, channels only. + pub can_post_messages: Option, + + /// `true`, if the administrator can edit messages of + /// other users and can pin messages, channels only. + pub can_edit_messages: Option, + + /// `true`, if the administrator can delete messages + /// of other users. + pub can_delete_messages: bool, + + /// `true`, if the administrator can invite new users + /// to the chat. + pub can_invite_users: bool, + + /// `true`, if the administrator can restrict, + /// ban or unban chat members. + pub can_restrict_members: bool, + + /// `true`, if the administrator can pin messages, + /// supergroups only. + pub can_pin_messages: Option, + + /// `true`, if the administrator can add new + /// administrators with a subset of his own privileges or demote + /// administrators that he has promoted, directly or indirectly + /// (promoted by administrators that were appointed by the + /// user). + pub can_promote_members: bool, +} + +/// User, restricted in the group. This struct is part of the [`ChatmemberKind`] +/// enum. +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct Restricted { + /// Date when restrictions will be lifted for + /// this user, unix time. + pub until_date: i32, + + /// Restricted only. `true`, if the user can send text messages, + /// contacts, locations and venues. + pub can_send_messages: bool, + + /// Restricted only. `true`, if the user is allowed to send audios, + /// documents, photos, videos, video notes and voice notes. + pub can_send_media_messages: bool, + + /// Restricted only. `true`, if the user is allowed to send animations, + /// games, stickers and use inline bots. + pub can_send_other_messages: bool, + + /// Restricted only. `true`, if the user is allowed to add web page + /// previews to their messages. + pub can_add_web_page_previews: bool, +} + +/// User kicked from the group. This struct is part of the [`ChatmemberKind`] +/// enum. +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct Kicked { + /// Date when restrictions will be lifted for + /// this user, unix time. + pub until_date: i32, } impl ChatMember { pub fn status(&self) -> ChatMemberStatus { match &self.kind { - ChatMemberKind::Creator { .. } => ChatMemberStatus::Creator, - ChatMemberKind::Administrator { .. } => ChatMemberStatus::Administrator, + ChatMemberKind::Creator(_) => ChatMemberStatus::Creator, + ChatMemberKind::Administrator(_) => ChatMemberStatus::Administrator, ChatMemberKind::Member => ChatMemberStatus::Member, - ChatMemberKind::Restricted { .. } => ChatMemberStatus::Restricted, + ChatMemberKind::Restricted(_) => ChatMemberStatus::Restricted, ChatMemberKind::Left => ChatMemberStatus::Left, - ChatMemberKind::Kicked { .. } => ChatMemberStatus::Kicked, + ChatMemberKind::Kicked(_) => ChatMemberStatus::Kicked, } } } + impl ChatMemberKind { + /// Getter for [`Administrator::custom_title`] and [`Creator::custom_title`] + /// fields. pub fn custom_title(&self) -> Option<&str> { match &self { - Self::Administrator { custom_title, .. } | Self::Creator { custom_title, .. } => { - //Some(custom_title.as_str()) - custom_title.as_deref() - } - Self::Member | Self::Restricted { .. } | Self::Left | Self::Kicked { .. } => None, + Self::Administrator(Administrator { custom_title, .. }) + | Self::Creator(Creator { custom_title, .. }) => custom_title.as_deref(), + Self::Member | Self::Restricted(_) | Self::Left | Self::Kicked(_) => None, } } + /// Getter for [`Restricted::until_date`] and [`Kicked::until_date`] fields. pub fn until_date(&self) -> Option { match &self { - Self::Creator { .. } | Self::Administrator { .. } | Self::Member | Self::Left => None, - Self::Restricted { until_date, .. } | Self::Kicked { until_date, .. } => { - Some(*until_date) - } + Self::Creator(_) | Self::Administrator(_) | Self::Member | Self::Left => None, + Self::Restricted(Restricted { until_date, .. }) + | Self::Kicked(Kicked { until_date, .. }) => Some(*until_date), } } + /// Getter for [`Administrator::can_be_edited`] field. pub fn can_be_edited(&self) -> Option { match &self { - Self::Administrator { can_be_edited, .. } => Some(*can_be_edited), - Self::Creator { .. } + Self::Administrator(Administrator { can_be_edited, .. }) => Some(*can_be_edited), + Self::Creator(_) | Self::Member - | Self::Restricted { .. } + | Self::Restricted(_) | Self::Left - | Self::Kicked { .. } => None, + | Self::Kicked(_) => None, } } + /// Getter for [`Administrator::can_change_info`] field. pub fn can_change_info(&self) -> Option { match &self { - Self::Administrator { can_change_info, .. } => Some(*can_change_info), - Self::Creator { .. } + Self::Administrator(Administrator { can_change_info, .. }) => Some(*can_change_info), + Self::Creator(_) | Self::Member - | Self::Restricted { .. } + | Self::Restricted(_) | Self::Left - | Self::Kicked { .. } => None, + | Self::Kicked(_) => None, } } + /// Getter for [`Administrator::can_post_messages`] field. pub fn can_post_messages(&self) -> Option { match &self { - Self::Administrator { can_post_messages, .. } => *can_post_messages, - Self::Creator { .. } + Self::Administrator(Administrator { can_post_messages, .. }) => *can_post_messages, + Self::Creator(_) | Self::Member - | Self::Restricted { .. } + | Self::Restricted(_) | Self::Left - | Self::Kicked { .. } => None, + | Self::Kicked(_) => None, } } + /// Getter for [`Administrator::can_edit_messages`] field. pub fn can_edit_messages(&self) -> Option { match &self { - Self::Administrator { can_edit_messages, .. } => *can_edit_messages, - Self::Creator { .. } + Self::Administrator(Administrator { can_edit_messages, .. }) => *can_edit_messages, + Self::Creator(_) | Self::Member - | Self::Restricted { .. } + | Self::Restricted(_) | Self::Left - | Self::Kicked { .. } => None, + | Self::Kicked(_) => None, } } + /// Getter for [`Administrator::can_delete_messages`] field. pub fn can_delete_messages(&self) -> Option { match &self { - Self::Administrator { can_delete_messages, .. } => Some(*can_delete_messages), - Self::Creator { .. } + Self::Administrator(Administrator { can_delete_messages, .. }) => { + Some(*can_delete_messages) + } + Self::Creator(_) | Self::Member - | Self::Restricted { .. } + | Self::Restricted(_) | Self::Left - | Self::Kicked { .. } => None, + | Self::Kicked(_) => None, } } + /// Getter for [`Administrator::can_invite_users`] field. pub fn can_invite_users(&self) -> Option { match &self { - Self::Administrator { can_invite_users, .. } => Some(*can_invite_users), - Self::Creator { .. } + Self::Administrator(Administrator { can_invite_users, .. }) => Some(*can_invite_users), + Self::Creator(_) | Self::Member - | Self::Restricted { .. } + | Self::Restricted(_) | Self::Left - | Self::Kicked { .. } => None, + | Self::Kicked(_) => None, } } + /// Getter for [`Administrator::can_restrict_members`] field. pub fn can_restrict_members(&self) -> Option { match &self { - Self::Administrator { can_restrict_members, .. } => Some(*can_restrict_members), - Self::Creator { .. } + Self::Administrator(Administrator { can_restrict_members, .. }) => { + Some(*can_restrict_members) + } + Self::Creator(_) | Self::Member - | Self::Restricted { .. } + | Self::Restricted(_) | Self::Left - | Self::Kicked { .. } => None, + | Self::Kicked(_) => None, } } + /// Getter for [`Administrator::can_pin_messages`] field. pub fn can_pin_messages(&self) -> Option { match &self { - Self::Administrator { can_pin_messages, .. } => *can_pin_messages, - Self::Creator { .. } + Self::Administrator(Administrator { can_pin_messages, .. }) => *can_pin_messages, + Self::Creator(_) | Self::Member - | Self::Restricted { .. } + | Self::Restricted(_) | Self::Left - | Self::Kicked { .. } => None, + | Self::Kicked(_) => None, } } + /// Getter for [`Administrator::can_promote_members`] field. pub fn can_promote_members(&self) -> Option { match &self { - Self::Administrator { can_promote_members, .. } => Some(*can_promote_members), - Self::Creator { .. } + Self::Administrator(Administrator { can_promote_members, .. }) => { + Some(*can_promote_members) + } + Self::Creator(_) | Self::Member - | Self::Restricted { .. } + | Self::Restricted(_) | Self::Left - | Self::Kicked { .. } => None, + | Self::Kicked(_) => None, } } + /// Getter for [`Restricted::can_send_messages`] field. pub fn can_send_messages(&self) -> Option { match &self { - Self::Restricted { can_send_messages, .. } => Some(*can_send_messages), - Self::Creator { .. } - | Self::Administrator { .. } + Self::Restricted(Restricted { can_send_messages, .. }) => Some(*can_send_messages), + Self::Creator(_) + | Self::Administrator(_) | Self::Member | Self::Left - | Self::Kicked { .. } => None, + | Self::Kicked(_) => None, } } + /// Getter for [`Restricted::can_send_media_messages`] field. pub fn can_send_media_messages(&self) -> Option { match &self { - Self::Restricted { can_send_media_messages, .. } => Some(*can_send_media_messages), - Self::Creator { .. } - | Self::Administrator { .. } + Self::Restricted(Restricted { can_send_media_messages, .. }) => { + Some(*can_send_media_messages) + } + Self::Creator(_) + | Self::Administrator(_) | Self::Member | Self::Left - | Self::Kicked { .. } => None, + | Self::Kicked(_) => None, } } + /// Getter for [`Restricted::can_send_other_messages`] field. pub fn can_send_other_messages(&self) -> Option { match &self { - Self::Restricted { can_send_other_messages, .. } => Some(*can_send_other_messages), - Self::Creator { .. } - | Self::Administrator { .. } + Self::Restricted(Restricted { can_send_other_messages, .. }) => { + Some(*can_send_other_messages) + } + Self::Creator(_) + | Self::Administrator(_) | Self::Member | Self::Left - | Self::Kicked { .. } => None, + | Self::Kicked(_) => None, } } + /// Getter for [`Restricted::can_add_web_page_previews`] field. pub fn can_add_web_page_previews(&self) -> Option { match &self { - Self::Restricted { can_add_web_page_previews, .. } => Some(*can_add_web_page_previews), - Self::Creator { .. } - | Self::Administrator { .. } + Self::Restricted(Restricted { can_add_web_page_previews, .. }) => { + Some(*can_add_web_page_previews) + } + Self::Creator(_) + | Self::Administrator(_) | Self::Member | Self::Left - | Self::Kicked { .. } => None, + | Self::Kicked(_) => None, } } } @@ -316,7 +361,7 @@ mod tests { username: Some("fl".to_string()), language_code: Some("en".to_string()), }, - kind: ChatMemberKind::Administrator { + kind: ChatMemberKind::Administrator(Administrator { custom_title: None, can_be_edited: false, can_change_info: true, @@ -327,7 +372,7 @@ mod tests { can_restrict_members: true, can_pin_messages: Some(true), can_promote_members: true, - }, + }), }; let actual = serde_json::from_str::(&json).unwrap(); assert_eq!(actual, expected) From e54f45ceabb9654002ce765a1a819a0122e88e27 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 20 Oct 2020 12:17:07 +0300 Subject: [PATCH 125/755] Make the trait bounds in Request{,er} stricter --- src/bot/api.rs | 2 ++ src/bot/auto_send.rs | 2 ++ src/bot/cache_me.rs | 12 ++++++++++-- src/bot/limits.rs | 12 +++++++++--- src/local_macros.rs | 2 +- src/requests/request.rs | 6 +++--- src/requests/requester.rs | 6 ++++-- 7 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/bot/api.rs b/src/bot/api.rs index 5e2cb0ad..e32bdbe9 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -1705,6 +1705,8 @@ impl Bot { } impl Requester for Bot { + type Err = crate::errors::RequestError; + type GetMe = JsonRequest; fn get_me(&self) -> JsonRequest { diff --git a/src/bot/auto_send.rs b/src/bot/auto_send.rs index 5f2ceb49..98e14ad2 100644 --- a/src/bot/auto_send.rs +++ b/src/bot/auto_send.rs @@ -63,6 +63,8 @@ impl AutoSend { } impl Requester for AutoSend { + type Err = B::Err; + type GetMe = AutoRequest; fn get_me(&self) -> Self::GetMe { diff --git a/src/bot/cache_me.rs b/src/bot/cache_me.rs index 8a06ebf2..b43061e4 100644 --- a/src/bot/cache_me.rs +++ b/src/bot/cache_me.rs @@ -54,7 +54,12 @@ impl CacheMe { } } -impl Requester for CacheMe { +impl Requester for CacheMe +where + B: Requester, +{ + type Err = B::Err; + type GetMe = CachedMeRequest; fn get_me(&self) -> Self::GetMe { @@ -85,7 +90,10 @@ enum Inner> { Pending(R, Arc>), } -impl> Request for CachedMeRequest { +impl Request for CachedMeRequest +where + R: Request, +{ type Err = R::Err; type Send = Send; type SendRef = SendRef; diff --git a/src/bot/limits.rs b/src/bot/limits.rs index 52f06d44..412057a9 100644 --- a/src/bot/limits.rs +++ b/src/bot/limits.rs @@ -346,7 +346,12 @@ impl Throttle { } } -impl Requester for Throttle { +impl Requester for Throttle +where + B::SendMessage: Send, +{ + type Err = B::Err; + type GetMe = B::GetMe; fn get_me(&self) -> Self::GetMe { @@ -412,8 +417,9 @@ impl HasPayload for ThrottlingRequest { } } -impl Request for ThrottlingRequest +impl Request for ThrottlingRequest where + R: Request + Send, ::Payload: GetChatId, { type Err = R::Err; @@ -617,7 +623,7 @@ mod chan_send { pub(super) struct ChanSend(#[pin] Inner); #[cfg(not(feature = "nightly"))] - type Inner = Pin)>>>>>; + type Inner = Pin)>>> + Send>>; #[cfg(feature = "nightly")] type Inner = impl Future)>>>; diff --git a/src/local_macros.rs b/src/local_macros.rs index c2aed978..5a48a207 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -83,7 +83,7 @@ macro_rules! req_future { #[cfg(not(feature = "nightly"))] pub(crate) type $i<$T> - $(where $($wh)*)? = ::core::pin::Pin>>; + $(where $($wh)*)? = ::core::pin::Pin + ::core::marker::Send + 'static>>; #[cfg(not(feature = "nightly"))] pub(crate) fn def<$T>($( $arg: $ArgTy ),*) -> $i<$T> diff --git a/src/requests/request.rs b/src/requests/request.rs index 8722a3e2..6abb457e 100644 --- a/src/requests/request.rs +++ b/src/requests/request.rs @@ -22,16 +22,16 @@ pub trait Request: HasPayload { */ /// Type of error that may happen during sending the request to telegram. - type Err: std::error::Error; + type Err: std::error::Error + Send; /// Type of future returned by [`send`](Request::send) method. - type Send: Future, Self::Err>>; + type Send: Future, Self::Err>> + Send; /// Type of future returned by [`send_ref`](Request::send_ref) method. /// /// NOTE: it intentionally forbids borrowing from self // though anyway we couldn't allow borrowing without GATs :sob: - type SendRef: Future, Self::Err>>; + type SendRef: Future, Self::Err>> + Send; /// Send the request. /// diff --git a/src/requests/requester.rs b/src/requests/requester.rs index f9f342d1..91f297af 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -10,12 +10,14 @@ use crate::{ /// _This trait is included in the crate's [`prelude`](crate::prelude)_. #[cfg_attr(all(docsrs, feature = "nightly"), doc(spotlight))] pub trait Requester { - type GetMe: Request; + type Err: std::error::Error + Send; + + type GetMe: Request; /// For telegram documentation of the method see [`GetMe`]. fn get_me(&self) -> Self::GetMe; - type SendMessage: Request; + type SendMessage: Request; /// For telegram documentation of the method see [`SendMessage`]. fn send_message(&self, chat_id: C, text: T) -> Self::SendMessage From 193dd692657f14627d388ed2e6955973c6c44054 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 20 Oct 2020 15:07:10 +0300 Subject: [PATCH 126/755] Cleanup errors --- src/errors.rs | 969 ++++++++++++++++++----------------- src/lib.rs | 2 +- src/local_macros.rs | 82 +++ src/net/mod.rs | 4 +- src/net/telegram_response.rs | 31 +- 5 files changed, 592 insertions(+), 496 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 999692be..fe749693 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -8,9 +8,11 @@ use thiserror::Error; /// An error caused by downloading a file. #[derive(Debug, Error, From)] pub enum DownloadError { + /// Network error while downloading file from telegram. #[error("A network error: {0}")] NetworkError(#[source] reqwest::Error), + /// I/O error while writing file to destination. #[error("An I/O error: {0}")] Io(#[source] std::io::Error), } @@ -18,8 +20,14 @@ pub enum DownloadError { /// An error caused by sending a request to Telegram. #[derive(Debug, Error)] pub enum RequestError { + /// Telegram API error #[error("A Telegram's error #{status_code}: {kind:?}")] - ApiError { status_code: StatusCode, kind: ApiErrorKind }, + ApiError { + /// Kind of api error + kind: ApiError, + /// HTTP code returned by telegram, not very usefull in practice. + status_code: StatusCode , + }, /// The group has been migrated to a supergroup with the specified /// identifier. @@ -31,9 +39,16 @@ pub enum RequestError { #[error("Retry after {0} seconds")] RetryAfter(i32), + /// Network error while sending request to telegram. #[error("A network error: {0}")] NetworkError(#[source] reqwest::Error), + /// Error while parsing response from telegram. + /// + /// If you've received this error, please, [open an issue] with the + /// description of the error. + /// + /// [open an issue]: https://github.com/teloxide/teloxide/issues/new #[error("An error while parsing JSON: {0}")] InvalidJson(#[source] serde_json::Error), @@ -42,480 +57,480 @@ pub enum RequestError { Io(#[source] io::Error), } -/// A kind of an API error. -/// -/// If you receive [`ApiErrorKind::Unknown`], please [open an issue] with -/// the description of the error. -/// -/// [`ApiErrorKind::Unknown`]: crate::ApiErrorKind::Unknown -/// [open an issue]: https://github.com/teloxide/teloxide/issues/new -#[derive(Debug, Deserialize, PartialEq, Hash, Eq, Clone)] -#[serde(untagged)] -pub enum ApiErrorKind { - Known(KnownApiErrorKind), - Unknown(String), -} - -/// A kind of a known API error. -#[derive(Debug, Deserialize, PartialEq, Copy, Hash, Eq, Clone)] -pub enum KnownApiErrorKind { - /// Occurs when the bot tries to send message to user who blocked the bot. - #[serde(rename = "Forbidden: bot was blocked by the user")] - BotBlocked, - - /// Occurs when bot tries to modify a message without modification content. - /// - /// May happen in methods: - /// 1. [`EditMessageText`] - /// - /// [`EditMessageText`]: crate::requests::EditMessageText - #[serde(rename = "Bad Request: message is not modified: specified new message content and \ - reply markup are exactly the same as a current content and reply markup \ - of the message")] - MessageNotModified, - - /// Occurs when bot tries to forward or delete a message which was deleted. - /// - /// May happen in methods: - /// 1. [`ForwardMessage`] - /// 2. [`DeleteMessage`] - /// - /// [`ForwardMessage`]: crate::requests::ForwardMessage - /// [`DeleteMessage`]: crate::requests::DeleteMessage - #[serde(rename = "Bad Request: MESSAGE_ID_INVALID")] - MessageIdInvalid, - - /// Occurs when bot tries to forward a message which does not exists. - /// - /// May happen in methods: - /// 1. [`ForwardMessage`] - /// - /// [`ForwardMessage`]: crate::requests::ForwardMessage - #[serde(rename = "Bad Request: message to forward not found")] - MessageToForwardNotFound, - - /// Occurs when bot tries to delete a message which does not exists. - /// - /// May happen in methods: - /// 1. [`DeleteMessage`] - /// - /// [`DeleteMessage`]: crate::requests::DeleteMessage - #[serde(rename = "Bad Request: message to delete not found")] - MessageToDeleteNotFound, - - /// Occurs when bot tries to send a text message without text. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: message text is empty")] - MessageTextIsEmpty, - - /// Occurs when bot tries to edit a message after long time. - /// - /// May happen in methods: - /// 1. [`EditMessageText`] - /// - /// [`EditMessageText`]: crate::requests::EditMessageText - #[serde(rename = "Bad Request: message can't be edited")] - MessageCantBeEdited, - - /// Occurs when bot tries to delete a someone else's message in group where - /// it does not have enough rights. - /// - /// May happen in methods: - /// 1. [`DeleteMessage`] - /// - /// [`DeleteMessage`]: crate::requests::DeleteMessage - #[serde(rename = "Bad Request: message can't be deleted")] - MessageCantBeDeleted, - - /// Occurs when bot tries to edit a message which does not exists. - /// - /// May happen in methods: - /// 1. [`EditMessageText`] - /// - /// [`EditMessageText`]: crate::requests::EditMessageText - #[serde(rename = "Bad Request: message to edit not found")] - MessageToEditNotFound, - - /// Occurs when bot tries to reply to a message which does not exists. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: reply message not found")] - MessageToReplyNotFound, - - /// Occurs when bot tries to - #[serde(rename = "Bad Request: message identifier is not specified")] - MessageIdentifierNotSpecified, - - /// Occurs when bot tries to send a message with text size greater then - /// 4096 symbols. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: message is too long")] - MessageIsTooLong, - - /// Occurs when bot tries to send media group with more than 10 items. - /// - /// May happen in methods: - /// 1. [`SendMediaGroup`] - /// - /// [`SendMediaGroup`]: crate::requests::SendMediaGroup - #[serde(rename = "Bad Request: Too much messages to send as an album")] - ToMuchMessages, - - /// Occurs when bot tries to stop poll that has already been stopped. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll has already been closed")] - PollHasAlreadyClosed, - - /// Occurs when bot tries to send poll with less than 2 options. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll must have at least 2 option")] - PollMustHaveMoreOptions, - - /// Occurs when bot tries to send poll with more than 10 options. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll can't have more than 10 options")] - PollCantHaveMoreOptions, - - /// Occurs when bot tries to send poll with empty option (without text). - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll options must be non-empty")] - PollOptionsMustBeNonEmpty, - - /// Occurs when bot tries to send poll with empty question (without text). - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll question must be non-empty")] - PollQuestionMustBeNonEmpty, - - /// Occurs when bot tries to send poll with total size of options more than - /// 100 symbols. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll options length must not exceed 100")] - PollOptionsLengthTooLong, - - /// Occurs when bot tries to send poll with question size more than 255 - /// symbols. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll question length must not exceed 255")] - PollQuestionLengthTooLong, - - /// Occurs when bot tries to stop poll with message without poll. - /// - /// May happen in methods: - /// 1. [`StopPoll`] - /// - /// [`StopPoll`]: crate::requests::StopPoll - #[serde(rename = "Bad Request: message with poll to stop not found")] - MessageWithPollNotFound, - - /// Occurs when bot tries to stop poll with message without poll. - /// - /// May happen in methods: - /// 1. [`StopPoll`] - /// - /// [`StopPoll`]: crate::requests::StopPoll - #[serde(rename = "Bad Request: message is not a poll")] - MessageIsNotAPoll, - - /// Occurs when bot tries to send a message to chat in which it is not a - /// member. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: chat not found")] - ChatNotFound, - - /// Occurs when bot tries to send method with unknown user_id. - /// - /// May happen in methods: - /// 1. [`getUserProfilePhotos`] - /// - /// [`getUserProfilePhotos`]: - /// crate::requests::GetUserProfilePhotos - #[serde(rename = "Bad Request: user not found")] - UserNotFound, - - /// Occurs when bot tries to send [`SetChatDescription`] with same text as - /// in the current description. - /// - /// May happen in methods: - /// 1. [`SetChatDescription`] - /// - /// [`SetChatDescription`]: crate::requests::SetChatDescription - #[serde(rename = "Bad Request: chat description is not modified")] - ChatDescriptionIsNotModified, - - /// Occurs when bot tries to answer to query after timeout expire. - /// - /// May happen in methods: - /// 1. [`AnswerCallbackQuery`] - /// - /// [`AnswerCallbackQuery`]: crate::requests::AnswerCallbackQuery - #[serde(rename = "Bad Request: query is too old and response timeout expired or query id is \ - invalid")] - InvalidQueryID, - - /// Occurs when bot tries to send InlineKeyboardMarkup with invalid button - /// url. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: BUTTON_URL_INVALID")] - ButtonURLInvalid, - - /// Occurs when bot tries to send button with data size more than 64 bytes. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: BUTTON_DATA_INVALID")] - ButtonDataInvalid, - - /// Occurs when bot tries to send button with data size == 0. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: can't parse inline keyboard button: Text buttons are \ - unallowed in the inline keyboard")] - TextButtonsAreUnallowed, - - /// Occurs when bot tries to get file by wrong file id. - /// - /// May happen in methods: - /// 1. [`GetFile`] - /// - /// [`GetFile`]: crate::requests::GetFile - #[serde(rename = "Bad Request: wrong file id")] - WrongFileID, - - /// Occurs when bot tries to do some with group which was deactivated. - #[serde(rename = "Bad Request: group is deactivated")] - GroupDeactivated, - - /// Occurs when bot tries to set chat photo from file ID - /// - /// May happen in methods: - /// 1. [`SetChatPhoto`] - /// - /// [`SetChatPhoto`]: crate::requests::SetChatPhoto - #[serde(rename = "Bad Request: Photo should be uploaded as an InputFile")] - PhotoAsInputFileRequired, - - /// Occurs when bot tries to add sticker to stickerset by invalid name. - /// - /// May happen in methods: - /// 1. [`AddStickerToSet`] - /// - /// [`AddStickerToSet`]: crate::requests::AddStickerToSet - #[serde(rename = "Bad Request: STICKERSET_INVALID")] - InvalidStickersSet, - - /// Occurs when bot tries to pin a message without rights to pin in this - /// chat. - /// - /// May happen in methods: - /// 1. [`PinChatMessage`] - /// - /// [`PinChatMessage`]: crate::requests::PinChatMessage - #[serde(rename = "Bad Request: not enough rights to pin a message")] - NotEnoughRightsToPinMessage, - - /// Occurs when bot tries to use method in group which is allowed only in a - /// supergroup or channel. - #[serde(rename = "Bad Request: method is available only for supergroups and channel")] - MethodNotAvailableInPrivateChats, - - /// Occurs when bot tries to demote chat creator. - /// - /// May happen in methods: - /// 1. [`PromoteChatMember`] - /// - /// [`PromoteChatMember`]: crate::requests::PromoteChatMember - #[serde(rename = "Bad Request: can't demote chat creator")] - CantDemoteChatCreator, - - /// Occurs when bot tries to restrict self in group chats. - /// - /// May happen in methods: - /// 1. [`RestrictChatMember`] - /// - /// [`RestrictChatMember`]: crate::requests::RestrictChatMember - #[serde(rename = "Bad Request: can't restrict self")] - CantRestrictSelf, - - /// Occurs when bot tries to restrict chat member without rights to - /// restrict in this chat. - /// - /// May happen in methods: - /// 1. [`RestrictChatMember`] - /// - /// [`RestrictChatMember`]: crate::requests::RestrictChatMember - #[serde(rename = "Bad Request: not enough rights to restrict/unrestrict chat member")] - NotEnoughRightsToRestrict, - - /// Occurs when bot tries set webhook to protocol other than HTTPS. - /// - /// May happen in methods: - /// 1. [`SetWebhook`] - /// - /// [`SetWebhook`]: crate::requests::SetWebhook - #[serde(rename = "Bad Request: bad webhook: HTTPS url must be provided for webhook")] - WebhookRequireHTTPS, - - /// Occurs when bot tries to set webhook to port other than 80, 88, 443 or - /// 8443. - /// - /// May happen in methods: - /// 1. [`SetWebhook`] - /// - /// [`SetWebhook`]: crate::requests::SetWebhook - #[serde(rename = "Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 \ - or 8443")] - BadWebhookPort, - - /// Occurs when bot tries to set webhook to unknown host. - /// - /// May happen in methods: - /// 1. [`SetWebhook`] - /// - /// [`SetWebhook`]: crate::requests::SetWebhook - #[serde(rename = "Bad Request: bad webhook: Failed to resolve host: Name or service not known")] - UnknownHost, - - /// Occurs when bot tries to set webhook to invalid URL. - /// - /// May happen in methods: - /// 1. [`SetWebhook`] - /// - /// [`SetWebhook`]: crate::requests::SetWebhook - #[serde(rename = "Bad Request: can't parse URL")] - CantParseUrl, - - /// Occurs when bot tries to send message with unfinished entities. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: can't parse entities")] - CantParseEntities, - - /// Occurs when bot tries to use getUpdates while webhook is active. - /// - /// May happen in methods: - /// 1. [`GetUpdates`] - /// - /// [`GetUpdates`]: crate::requests::GetUpdates - #[serde(rename = "can't use getUpdates method while webhook is active")] - CantGetUpdates, - - /// Occurs when bot tries to do some in group where bot was kicked. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Unauthorized: bot was kicked from a chat")] - BotKicked, - - /// Occurs when bot tries to send message to deactivated user. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Unauthorized: user is deactivated")] - UserDeactivated, - - /// Occurs when you tries to initiate conversation with a user. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Unauthorized: bot can't initiate conversation with a user")] - CantInitiateConversation, - - /// Occurs when you tries to send message to bot. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Unauthorized: bot can't send messages to bots")] - CantTalkWithBots, - - /// Occurs when bot tries to send button with invalid http url. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: wrong HTTP URL")] - WrongHTTPurl, - - /// Occurs when bot tries GetUpdate before the timeout. Make sure that only - /// one Updater is running. - /// - /// May happen in methods: - /// 1. [`GetUpdates`] - /// - /// [`GetUpdates`]: crate::requests::GetUpdates - #[serde(rename = "Conflict: terminated by other getUpdates request; make sure that only one \ - bot instance is running")] - TerminatedByOtherGetUpdates, - - /// Occurs when bot tries to get file by invalid file id. - /// - /// May happen in methods: - /// 1. [`GetFile`] - /// - /// [`GetFile`]: crate::requests::GetFile - #[serde(rename = "Bad Request: invalid file id")] - FileIdInvalid, +serde_or_unknown! { + #[unknown_mod = api_error_internals] + #[unknown_kind = Kind] + #[unknown_path = "api_error_internals::Kind"] + + /// A kind of an API error. + #[derive(Debug, Deserialize, PartialEq, Hash, Eq, Clone)] + pub enum ApiError { + #[unknown] + /// Error which is not known to `teloxide`. + /// + /// If you've received this error, please [open an issue] with the description of the error. + /// + /// [open an issue]: https://github.com/teloxide/teloxide/issues/new + Unknown(String), + + /// Occurs when the bot tries to send message to user who blocked the bot. + #[serde(rename = "Forbidden: bot was blocked by the user")] + BotBlocked, + + /// Occurs when bot tries to modify a message without modification content. + /// + /// May happen in methods: + /// 1. [`EditMessageText`] + /// + /// [`EditMessageText`]: crate::requests::EditMessageText + #[serde(rename = "Bad Request: message is not modified: specified new message content and \ + reply markup are exactly the same as a current content and reply markup \ + of the message")] + MessageNotModified, + + /// Occurs when bot tries to forward or delete a message which was deleted. + /// + /// May happen in methods: + /// 1. [`ForwardMessage`] + /// 2. [`DeleteMessage`] + /// + /// [`ForwardMessage`]: crate::requests::ForwardMessage + /// [`DeleteMessage`]: crate::requests::DeleteMessage + #[serde(rename = "Bad Request: MESSAGE_ID_INVALID")] + MessageIdInvalid, + + /// Occurs when bot tries to forward a message which does not exists. + /// + /// May happen in methods: + /// 1. [`ForwardMessage`] + /// + /// [`ForwardMessage`]: crate::requests::ForwardMessage + #[serde(rename = "Bad Request: message to forward not found")] + MessageToForwardNotFound, + + /// Occurs when bot tries to delete a message which does not exists. + /// + /// May happen in methods: + /// 1. [`DeleteMessage`] + /// + /// [`DeleteMessage`]: crate::requests::DeleteMessage + #[serde(rename = "Bad Request: message to delete not found")] + MessageToDeleteNotFound, + + /// Occurs when bot tries to send a text message without text. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: message text is empty")] + MessageTextIsEmpty, + + /// Occurs when bot tries to edit a message after long time. + /// + /// May happen in methods: + /// 1. [`EditMessageText`] + /// + /// [`EditMessageText`]: crate::requests::EditMessageText + #[serde(rename = "Bad Request: message can't be edited")] + MessageCantBeEdited, + + /// Occurs when bot tries to delete a someone else's message in group where + /// it does not have enough rights. + /// + /// May happen in methods: + /// 1. [`DeleteMessage`] + /// + /// [`DeleteMessage`]: crate::requests::DeleteMessage + #[serde(rename = "Bad Request: message can't be deleted")] + MessageCantBeDeleted, + + /// Occurs when bot tries to edit a message which does not exists. + /// + /// May happen in methods: + /// 1. [`EditMessageText`] + /// + /// [`EditMessageText`]: crate::requests::EditMessageText + #[serde(rename = "Bad Request: message to edit not found")] + MessageToEditNotFound, + + /// Occurs when bot tries to reply to a message which does not exists. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: reply message not found")] + MessageToReplyNotFound, + + /// Occurs when bot tries to + #[serde(rename = "Bad Request: message identifier is not specified")] + MessageIdentifierNotSpecified, + + /// Occurs when bot tries to send a message with text size greater then + /// 4096 symbols. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: message is too long")] + MessageIsTooLong, + + /// Occurs when bot tries to send media group with more than 10 items. + /// + /// May happen in methods: + /// 1. [`SendMediaGroup`] + /// + /// [`SendMediaGroup`]: crate::requests::SendMediaGroup + #[serde(rename = "Bad Request: Too much messages to send as an album")] + ToMuchMessages, + + /// Occurs when bot tries to stop poll that has already been stopped. + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll has already been closed")] + PollHasAlreadyClosed, + + /// Occurs when bot tries to send poll with less than 2 options. + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll must have at least 2 option")] + PollMustHaveMoreOptions, + + /// Occurs when bot tries to send poll with more than 10 options. + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll can't have more than 10 options")] + PollCantHaveMoreOptions, + + /// Occurs when bot tries to send poll with empty option (without text). + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll options must be non-empty")] + PollOptionsMustBeNonEmpty, + + /// Occurs when bot tries to send poll with empty question (without text). + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll question must be non-empty")] + PollQuestionMustBeNonEmpty, + + /// Occurs when bot tries to send poll with total size of options more than + /// 100 symbols. + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll options length must not exceed 100")] + PollOptionsLengthTooLong, + + /// Occurs when bot tries to send poll with question size more than 255 + /// symbols. + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll question length must not exceed 255")] + PollQuestionLengthTooLong, + + /// Occurs when bot tries to stop poll with message without poll. + /// + /// May happen in methods: + /// 1. [`StopPoll`] + /// + /// [`StopPoll`]: crate::requests::StopPoll + #[serde(rename = "Bad Request: message with poll to stop not found")] + MessageWithPollNotFound, + + /// Occurs when bot tries to stop poll with message without poll. + /// + /// May happen in methods: + /// 1. [`StopPoll`] + /// + /// [`StopPoll`]: crate::requests::StopPoll + #[serde(rename = "Bad Request: message is not a poll")] + MessageIsNotAPoll, + + /// Occurs when bot tries to send a message to chat in which it is not a + /// member. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: chat not found")] + ChatNotFound, + + /// Occurs when bot tries to send method with unknown user_id. + /// + /// May happen in methods: + /// 1. [`getUserProfilePhotos`] + /// + /// [`getUserProfilePhotos`]: + /// crate::requests::GetUserProfilePhotos + #[serde(rename = "Bad Request: user not found")] + UserNotFound, + + /// Occurs when bot tries to send [`SetChatDescription`] with same text as + /// in the current description. + /// + /// May happen in methods: + /// 1. [`SetChatDescription`] + /// + /// [`SetChatDescription`]: crate::requests::SetChatDescription + #[serde(rename = "Bad Request: chat description is not modified")] + ChatDescriptionIsNotModified, + + /// Occurs when bot tries to answer to query after timeout expire. + /// + /// May happen in methods: + /// 1. [`AnswerCallbackQuery`] + /// + /// [`AnswerCallbackQuery`]: crate::requests::AnswerCallbackQuery + #[serde(rename = "Bad Request: query is too old and response timeout expired or query id is \ + invalid")] + InvalidQueryID, + + /// Occurs when bot tries to send InlineKeyboardMarkup with invalid button + /// url. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: BUTTON_URL_INVALID")] + ButtonURLInvalid, + + /// Occurs when bot tries to send button with data size more than 64 bytes. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: BUTTON_DATA_INVALID")] + ButtonDataInvalid, + + /// Occurs when bot tries to send button with data size == 0. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: can't parse inline keyboard button: Text buttons are \ + unallowed in the inline keyboard")] + TextButtonsAreUnallowed, + + /// Occurs when bot tries to get file by wrong file id. + /// + /// May happen in methods: + /// 1. [`GetFile`] + /// + /// [`GetFile`]: crate::requests::GetFile + #[serde(rename = "Bad Request: wrong file id")] + WrongFileID, + + /// Occurs when bot tries to do some with group which was deactivated. + #[serde(rename = "Bad Request: group is deactivated")] + GroupDeactivated, + + /// Occurs when bot tries to set chat photo from file ID + /// + /// May happen in methods: + /// 1. [`SetChatPhoto`] + /// + /// [`SetChatPhoto`]: crate::requests::SetChatPhoto + #[serde(rename = "Bad Request: Photo should be uploaded as an InputFile")] + PhotoAsInputFileRequired, + + /// Occurs when bot tries to add sticker to stickerset by invalid name. + /// + /// May happen in methods: + /// 1. [`AddStickerToSet`] + /// + /// [`AddStickerToSet`]: crate::requests::AddStickerToSet + #[serde(rename = "Bad Request: STICKERSET_INVALID")] + InvalidStickersSet, + + /// Occurs when bot tries to pin a message without rights to pin in this + /// chat. + /// + /// May happen in methods: + /// 1. [`PinChatMessage`] + /// + /// [`PinChatMessage`]: crate::requests::PinChatMessage + #[serde(rename = "Bad Request: not enough rights to pin a message")] + NotEnoughRightsToPinMessage, + + /// Occurs when bot tries to use method in group which is allowed only in a + /// supergroup or channel. + #[serde(rename = "Bad Request: method is available only for supergroups and channel")] + MethodNotAvailableInPrivateChats, + + /// Occurs when bot tries to demote chat creator. + /// + /// May happen in methods: + /// 1. [`PromoteChatMember`] + /// + /// [`PromoteChatMember`]: crate::requests::PromoteChatMember + #[serde(rename = "Bad Request: can't demote chat creator")] + CantDemoteChatCreator, + + /// Occurs when bot tries to restrict self in group chats. + /// + /// May happen in methods: + /// 1. [`RestrictChatMember`] + /// + /// [`RestrictChatMember`]: crate::requests::RestrictChatMember + #[serde(rename = "Bad Request: can't restrict self")] + CantRestrictSelf, + + /// Occurs when bot tries to restrict chat member without rights to + /// restrict in this chat. + /// + /// May happen in methods: + /// 1. [`RestrictChatMember`] + /// + /// [`RestrictChatMember`]: crate::requests::RestrictChatMember + #[serde(rename = "Bad Request: not enough rights to restrict/unrestrict chat member")] + NotEnoughRightsToRestrict, + + /// Occurs when bot tries set webhook to protocol other than HTTPS. + /// + /// May happen in methods: + /// 1. [`SetWebhook`] + /// + /// [`SetWebhook`]: crate::requests::SetWebhook + #[serde(rename = "Bad Request: bad webhook: HTTPS url must be provided for webhook")] + WebhookRequireHTTPS, + + /// Occurs when bot tries to set webhook to port other than 80, 88, 443 or + /// 8443. + /// + /// May happen in methods: + /// 1. [`SetWebhook`] + /// + /// [`SetWebhook`]: crate::requests::SetWebhook + #[serde(rename = "Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 \ + or 8443")] + BadWebhookPort, + + /// Occurs when bot tries to set webhook to unknown host. + /// + /// May happen in methods: + /// 1. [`SetWebhook`] + /// + /// [`SetWebhook`]: crate::requests::SetWebhook + #[serde(rename = "Bad Request: bad webhook: Failed to resolve host: Name or service not known")] + UnknownHost, + + /// Occurs when bot tries to set webhook to invalid URL. + /// + /// May happen in methods: + /// 1. [`SetWebhook`] + /// + /// [`SetWebhook`]: crate::requests::SetWebhook + #[serde(rename = "Bad Request: can't parse URL")] + CantParseUrl, + + /// Occurs when bot tries to send message with unfinished entities. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: can't parse entities")] + CantParseEntities, + + /// Occurs when bot tries to use getUpdates while webhook is active. + /// + /// May happen in methods: + /// 1. [`GetUpdates`] + /// + /// [`GetUpdates`]: crate::requests::GetUpdates + #[serde(rename = "can't use getUpdates method while webhook is active")] + CantGetUpdates, + + /// Occurs when bot tries to do some in group where bot was kicked. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Unauthorized: bot was kicked from a chat")] + BotKicked, + + /// Occurs when bot tries to send message to deactivated user. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Unauthorized: user is deactivated")] + UserDeactivated, + + /// Occurs when you tries to initiate conversation with a user. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Unauthorized: bot can't initiate conversation with a user")] + CantInitiateConversation, + + /// Occurs when you tries to send message to bot. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Unauthorized: bot can't send messages to bots")] + CantTalkWithBots, + + /// Occurs when bot tries to send button with invalid http url. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: wrong HTTP URL")] + WrongHTTPurl, + + /// Occurs when bot tries GetUpdate before the timeout. Make sure that only + /// one Updater is running. + /// + /// May happen in methods: + /// 1. [`GetUpdates`] + /// + /// [`GetUpdates`]: crate::requests::GetUpdates + #[serde(rename = "Conflict: terminated by other getUpdates request; make sure that only one \ + bot instance is running")] + TerminatedByOtherGetUpdates, + + /// Occurs when bot tries to get file by invalid file id. + /// + /// May happen in methods: + /// 1. [`GetFile`] + /// + /// [`GetFile`]: crate::requests::GetFile + #[serde(rename = "Bad Request: invalid file id")] + FileIdInvalid, + } } diff --git a/src/lib.rs b/src/lib.rs index 6e7b78b0..e12421e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,7 @@ mod local_macros; // FIXME(waffle): rethink modules, find a place for wrappers. pub use self::{ bot::{AutoSend, Bot, BotBuilder, CacheMe}, - errors::{ApiErrorKind, DownloadError, KnownApiErrorKind, RequestError}, + errors::{ApiError, DownloadError, RequestError}, }; pub mod payloads; diff --git a/src/local_macros.rs b/src/local_macros.rs index c2aed978..0b3ac6fa 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -106,3 +106,85 @@ macro_rules! req_future { }; } + +#[macro_use] +macro_rules! serde_or_unknown { + ( + #[unknown_mod = $mod_:ident] + #[unknown_kind = $Kind:ident] + #[unknown_path = $path:literal] + $( + #[ $($meta:tt)* ] + )* + $v:vis enum $Name:ident { + #[unknown] + $( + #[ $($unknown_meta:tt)* ] + )* + $Unknown:ident(String) + + $( + , + $( + #[ $($var_meta:tt)* ] + )* + $Var:ident + )* + $(,)? + } + ) => { + mod $mod_ { + #[allow(unused_imports)] + use super::{*, $Name as _}; + + $( + #[ $($meta)* ] + )* + $v enum $Name { + $( + $( + #[ $($var_meta)* ] + )* + $Var, + )* + } + + #[derive(::serde::Deserialize)] + #[serde(untagged)] + $v enum $Kind { + Known($Name), + Unknown(String), + } + + impl ::core::convert::From<$Kind> for super::$Name { + fn from(kind: $Kind) -> Self { + match kind { + $Kind::Unknown(string) => Self::Unknown(string), + $Kind::Known(known) => match known { + $( + $Name::$Var => Self::$Var, + )* + } + } + } + } + } + + $( + #[ $($meta)* ] + )* + #[serde(from = $path)] + $v enum $Name { + $( + $( + #[ $($var_meta)* ] + )* + $Var, + )* + $( + #[ $($unknown_meta)* ] + )* + $Unknown(String), + } + } +} diff --git a/src/net/mod.rs b/src/net/mod.rs index de38dacc..97db838a 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -1,7 +1,7 @@ #[cfg(feature = "unstable-stream")] -pub use download::download_file_stream; +pub(crate) use download::download_file_stream; -pub use self::{ +pub(crate) use self::{ download::download_file, request::{request_json, request_json2, request_multipart, request_multipart2}, telegram_response::TelegramResponse, diff --git a/src/net/telegram_response.rs b/src/net/telegram_response.rs index d5062e8f..4bf3849b 100644 --- a/src/net/telegram_response.rs +++ b/src/net/telegram_response.rs @@ -4,18 +4,18 @@ use serde::Deserialize; use crate::{ requests::ResponseResult, types::{False, ResponseParameters, True}, - ApiErrorKind, RequestError, + ApiError, RequestError, }; #[derive(Deserialize)] #[serde(untagged)] -pub enum TelegramResponse { +pub(crate) enum TelegramResponse { Ok { /// A dummy field. Used only for deserialization. #[allow(dead_code)] ok: True, - result: R, + response: R, }, Err { /// A dummy field. Used only for deserialization. @@ -23,7 +23,7 @@ pub enum TelegramResponse { ok: False, #[serde(rename = "description")] - kind: ApiErrorKind, + error: ApiError, error_code: u16, response_parameters: Option, }, @@ -32,8 +32,8 @@ pub enum TelegramResponse { impl Into> for TelegramResponse { fn into(self) -> Result { match self { - TelegramResponse::Ok { result, .. } => Ok(result), - TelegramResponse::Err { kind, error_code, response_parameters, .. } => { + TelegramResponse::Ok { response, .. } => Ok(response), + TelegramResponse::Err { error, error_code, response_parameters, .. } => { if let Some(params) = response_parameters { match params { ResponseParameters::RetryAfter(i) => Err(RequestError::RetryAfter(i)), @@ -43,7 +43,7 @@ impl Into> for TelegramResponse { } } else { Err(RequestError::ApiError { - kind, + kind: error, status_code: StatusCode::from_u16(error_code).unwrap(), }) } @@ -55,16 +55,15 @@ impl Into> for TelegramResponse { #[cfg(test)] mod tests { use super::*; - use crate::{errors::KnownApiErrorKind, types::Update}; + use crate::{errors::ApiError, types::Update}; #[test] - fn terminated_by_other_get_updates() { - let expected = ApiErrorKind::Known(KnownApiErrorKind::TerminatedByOtherGetUpdates); - if let TelegramResponse::Err{ kind, .. } = serde_json::from_str::>(r#"{"ok":false,"error_code":409,"description":"Conflict: terminated by other getUpdates request; make sure that only one bot instance is running"}"#).unwrap() { - assert_eq!(expected, kind); - } - else { - panic!("Expected ApiErrorKind::TerminatedByOtherGetUpdates"); - } + fn parse_terminated_by_other_get_updates() { + let s = r#"{"ok":false,"error_code":409,"description":"Conflict: terminated by other getUpdates request; make sure that only one bot instance is running"}"#; + let val = serde_json::from_str::>(s).unwrap(); + + assert!( + matches!(val, TelegramResponse::Err { error: ApiError::TerminatedByOtherGetUpdates, .. }) + ); } } From 41afdb554d31131b4a20e92d1e3da6e6441ac3a1 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 20 Oct 2020 17:02:31 +0300 Subject: [PATCH 127/755] fmt --- src/errors.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index fe749693..da2666b2 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -22,11 +22,11 @@ pub enum DownloadError { pub enum RequestError { /// Telegram API error #[error("A Telegram's error #{status_code}: {kind:?}")] - ApiError { + ApiError { /// Kind of api error - kind: ApiError, + kind: ApiError, /// HTTP code returned by telegram, not very usefull in practice. - status_code: StatusCode , + status_code: StatusCode, }, /// The group has been migrated to a supergroup with the specified From e79f83d4a1a0597f23f67bab239d667d9a0b700c Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 20 Oct 2020 15:33:22 +0300 Subject: [PATCH 128/755] Move bot adaptors to own module and place then under features This commit moves `bot::{auto_send,cache_me,limits}` modules to `adaptors::{auto_send,cache_me,throttle}. ALso it adds 4 crate features: - `throttle` - `cache_me` - `auto_send` - `full` (enables all features, except `nightly`) --- Cargo.toml | 18 +++++++++++++++++- src/adaptors.rs | 19 +++++++++++++++++++ src/{bot => adaptors}/auto_send.rs | 0 src/{bot => adaptors}/cache_me.rs | 0 src/{bot/limits.rs => adaptors/throttle.rs} | 16 +++++----------- src/bot/mod.rs | 7 ------- src/lib.rs | 8 +++----- src/requests/requester_ext.rs | 3 +-- 8 files changed, 45 insertions(+), 26 deletions(-) create mode 100644 src/adaptors.rs rename src/{bot => adaptors}/auto_send.rs (100%) rename src/{bot => adaptors}/cache_me.rs (100%) rename src/{bot/limits.rs => adaptors/throttle.rs} (98%) diff --git a/Cargo.toml b/Cargo.toml index e25be772..d550d1f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,9 +39,25 @@ never = "0.1.0" vecrem = { git = "https://github.com/WaffleLapkin/vecrem", rev = "6b9b6f42342df8b75548c6ed387072ff235429b1" } [features] -# features those require nightly compiler +default = [] + +# Features which require nightly compiler. +# +# Currently the only used compiler feature is feature(type_alias_impl_trait) +# which allow implementing `Future`s without boxing. nightly = [] +# Throttling bot adaptor +throttle = [] + +# CacheMe bot adaptor +cache_me = [] + +# AutoSend bot adaptor +auto_send = [] + +full = ["throttle", "cache_me", "auto_send"] + [package.metadata."docs.rs"] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/src/adaptors.rs b/src/adaptors.rs new file mode 100644 index 00000000..3d7c4611 --- /dev/null +++ b/src/adaptors.rs @@ -0,0 +1,19 @@ +//! Bot adaptors + +#[cfg(feature = "auto_send")] +#[cfg_attr(docsrs, doc(cfg(feature = "auto_send")))] +pub mod auto_send; +#[cfg(feature = "cache_me")] +#[cfg_attr(docsrs, doc(cfg(feature = "cache_me")))] +pub mod cache_me; +#[cfg(feature = "throttle")] +#[cfg_attr(docsrs, doc(cfg(feature = "throttle")))] +pub mod throttle; + +pub use { + auto_send::AutoSend, + cache_me::CacheMe, + throttle::Throttle, +}; + +// FIXME: move default `parse_mode` to adaptor diff --git a/src/bot/auto_send.rs b/src/adaptors/auto_send.rs similarity index 100% rename from src/bot/auto_send.rs rename to src/adaptors/auto_send.rs diff --git a/src/bot/cache_me.rs b/src/adaptors/cache_me.rs similarity index 100% rename from src/bot/cache_me.rs rename to src/adaptors/cache_me.rs diff --git a/src/bot/limits.rs b/src/adaptors/throttle.rs similarity index 98% rename from src/bot/limits.rs rename to src/adaptors/throttle.rs index 412057a9..f1c40180 100644 --- a/src/bot/limits.rs +++ b/src/adaptors/throttle.rs @@ -18,7 +18,7 @@ use tokio::{ use vecrem::VecExt; use crate::{ - bot::limits::chan_send::{ChanSend, SendTy}, + adaptors::throttle::chan_send::{ChanSend, SendTy}, payloads::SendMessage, requests::{HasPayload, Output, Request, Requester}, types::ChatId, @@ -346,12 +346,7 @@ impl Throttle { } } -impl Requester for Throttle -where - B::SendMessage: Send, -{ - type Err = B::Err; - +impl Requester for Throttle { type GetMe = B::GetMe; fn get_me(&self) -> Self::GetMe { @@ -417,9 +412,8 @@ impl HasPayload for ThrottlingRequest { } } -impl Request for ThrottlingRequest +impl Request for ThrottlingRequest where - R: Request + Send, ::Payload: GetChatId, { type Err = R::Err; @@ -613,7 +607,7 @@ mod chan_send { use never::Never; use tokio::sync::{mpsc, mpsc::error::SendError, oneshot::Sender}; - use crate::bot::limits::Id; + use crate::adaptors::throttle::Id; pub(super) trait SendTy { fn send_t(self, val: (Id, Sender)) -> ChanSend; @@ -623,7 +617,7 @@ mod chan_send { pub(super) struct ChanSend(#[pin] Inner); #[cfg(not(feature = "nightly"))] - type Inner = Pin)>>> + Send>>; + type Inner = Pin)>>>>>; #[cfg(feature = "nightly")] type Inner = impl Future)>>>; diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 01698ed5..5c46e992 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -14,14 +14,7 @@ use crate::{ }; mod api; -mod auto_send; -mod cache_me; mod download; -mod limits; - -pub use auto_send::AutoSend; -pub use cache_me::CacheMe; -pub use limits::{Limits, Throttle}; pub(crate) const TELOXIDE_TOKEN: &str = "TELOXIDE_TOKEN"; pub(crate) const TELOXIDE_PROXY: &str = "TELOXIDE_PROXY"; diff --git a/src/lib.rs b/src/lib.rs index e12421e4..1b4d5dac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,7 @@ mod local_macros; // FIXME(waffle): rethink modules, find a place for wrappers. pub use self::{ - bot::{AutoSend, Bot, BotBuilder, CacheMe}, + bot::{Bot, BotBuilder}, errors::{ApiError, DownloadError, RequestError}, }; @@ -26,13 +26,11 @@ pub mod payloads; pub mod prelude; pub mod requests; pub mod types; - -// FIXME(waffle): made `pub` to reexport bot wrappers, in future we may want to -// reexport them from elsewhere -pub mod bot; +pub mod adaptors; // reexported mod errors; +mod bot; // implementation details mod net; diff --git a/src/requests/requester_ext.rs b/src/requests/requester_ext.rs index e6370846..00b41b32 100644 --- a/src/requests/requester_ext.rs +++ b/src/requests/requester_ext.rs @@ -1,7 +1,6 @@ use crate::{ - bot::{CacheMe, Limits, Throttle}, + adaptors::{CacheMe, throttle::Limits, Throttle, AutoSend}, requests::Requester, - AutoSend, }; pub trait RequesterExt: Requester { From 9bf8c02c9df2e87315048d8a23db8e6da59166f3 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 20 Oct 2020 16:52:29 +0300 Subject: [PATCH 129/755] doc & feature fixes - Remove `unstable-stream` feature - Doc fixes --- src/adaptors.rs | 31 ++++++++++++++++++++++--------- src/adaptors/throttle.rs | 4 +++- src/bot/download.rs | 15 +++++++-------- src/lib.rs | 5 +++-- src/net/download.rs | 3 +-- src/net/mod.rs | 5 +---- src/payloads/mod.rs | 1 + src/prelude.rs | 2 ++ src/requests/mod.rs | 2 +- src/requests/request.rs | 2 ++ src/requests/requester.rs | 2 +- src/requests/requester_ext.rs | 22 ++++++++++++++++++---- src/types/chat_member.rs | 8 ++++---- src/types/mod.rs | 2 +- 14 files changed, 67 insertions(+), 37 deletions(-) diff --git a/src/adaptors.rs b/src/adaptors.rs index 3d7c4611..09c4fec8 100644 --- a/src/adaptors.rs +++ b/src/adaptors.rs @@ -1,19 +1,32 @@ -//! Bot adaptors +//! ## Bot adaptors +//! +//! Bot adaptors are very similar to [`Iterator`] adaptors — +//! they are bots (implement [`Requester`]) which wrap other bots +//! adding new functionality. +//! +//! E.g. [`AutoSend`] allows `await`ing requests directly, +//! without need to use `.send()`. +//! +//! [`Requester`]: crate::requests::Requester #[cfg(feature = "auto_send")] -#[cfg_attr(docsrs, doc(cfg(feature = "auto_send")))] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "auto_send")))] pub mod auto_send; #[cfg(feature = "cache_me")] -#[cfg_attr(docsrs, doc(cfg(feature = "cache_me")))] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "cache_me")))] pub mod cache_me; #[cfg(feature = "throttle")] -#[cfg_attr(docsrs, doc(cfg(feature = "throttle")))] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "throttle")))] pub mod throttle; -pub use { - auto_send::AutoSend, - cache_me::CacheMe, - throttle::Throttle, -}; +#[cfg(feature = "auto_send")] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "auto_send")))] +pub use auto_send::AutoSend; +#[cfg(feature = "cache_me")] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "cache_me")))] +pub use cache_me::CacheMe; +#[cfg(feature = "throttle")] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "throttle")))] +pub use throttle::Throttle; // FIXME: move default `parse_mode` to adaptor diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index f1c40180..6d9fdd84 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -123,7 +123,7 @@ impl Default for Limits { /// ## Examples /// /// ```no_run (throttle fails to spawn task without tokio runtime) -/// use teloxide_core::{bot::Limits, requests::RequesterExt, Bot}; +/// use teloxide_core::{adaptors::throttle::Limits, requests::RequesterExt, Bot}; /// /// # #[allow(deprecated)] /// let bot = Bot::new("TOKEN").throttle(Limits::default()); @@ -316,6 +316,8 @@ impl Throttle { /// Creates new [`Throttle`] spawning the worker with `tokio::spawn` /// /// Note: it's recommended to use [`RequesterExt::throttle`] instead. + /// + /// [`RequesterExt::throttle`]: crate::requests::RequesterExt::throttle pub fn new_spawn(bot: B, limits: Limits) -> Self where // Basically, I hate this bound. diff --git a/src/bot/download.rs b/src/bot/download.rs index 241ef325..eed32b1c 100644 --- a/src/bot/download.rs +++ b/src/bot/download.rs @@ -1,11 +1,11 @@ -use tokio::io::AsyncWrite; +use bytes::Bytes; +use tokio::{io::AsyncWrite, stream::Stream}; -#[cfg(feature = "unstable-stream")] -use ::{bytes::Bytes, tokio::stream::Stream}; - -#[cfg(feature = "unstable-stream")] -use crate::net::download_file_stream; -use crate::{bot::Bot, net::download_file, DownloadError}; +use crate::{ + bot::Bot, + net::{download_file, download_file_stream}, + DownloadError, +}; impl Bot { /// Download a file from Telegram into `destination`. @@ -55,7 +55,6 @@ impl Bot { /// [`AsyncWrite`]: tokio::io::AsyncWrite /// [`tokio::fs::File`]: tokio::fs::File /// [`Bot::download_file`]: crate::Bot::download_file - #[cfg(feature = "unstable-stream")] pub async fn download_file_stream( &self, path: &str, diff --git a/src/lib.rs b/src/lib.rs index 1b4d5dac..ef19a9c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ #![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg, doc_spotlight))] #![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))] #![forbid(unsafe_code)] +#![cfg_attr(feature = "full", deny(broken_intra_doc_links))] //#![deny(missing_docs)] #[macro_use] @@ -22,15 +23,15 @@ pub use self::{ errors::{ApiError, DownloadError, RequestError}, }; +pub mod adaptors; pub mod payloads; pub mod prelude; pub mod requests; pub mod types; -pub mod adaptors; // reexported -mod errors; mod bot; +mod errors; // implementation details mod net; diff --git a/src/net/download.rs b/src/net/download.rs index 05f08f5d..aa54f4f0 100644 --- a/src/net/download.rs +++ b/src/net/download.rs @@ -27,12 +27,11 @@ where Ok(()) } -#[cfg(feature = "unstable-stream")] pub async fn download_file_stream( client: &Client, token: &str, path: &str, -) -> Result>, reqwest::Error> { +) -> Result>, reqwest::Error> { let res = client .get(&super::file_url(TELEGRAM_API_URL, token, path)) .send() diff --git a/src/net/mod.rs b/src/net/mod.rs index 97db838a..ede6cc0b 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -1,8 +1,5 @@ -#[cfg(feature = "unstable-stream")] -pub(crate) use download::download_file_stream; - pub(crate) use self::{ - download::download_file, + download::{download_file, download_file_stream}, request::{request_json, request_json2, request_multipart, request_multipart2}, telegram_response::TelegramResponse, }; diff --git a/src/payloads/mod.rs b/src/payloads/mod.rs index c468e562..22276002 100644 --- a/src/payloads/mod.rs +++ b/src/payloads/mod.rs @@ -1,3 +1,4 @@ +/// Payloads - data types sended to relegram pub mod setters; mod get_me; diff --git a/src/prelude.rs b/src/prelude.rs index f805598b..886b201a 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1 +1,3 @@ +//! Crate prelude + pub use crate::requests::Requester; diff --git a/src/requests/mod.rs b/src/requests/mod.rs index 2ba00350..cc134a48 100644 --- a/src/requests/mod.rs +++ b/src/requests/mod.rs @@ -1,4 +1,4 @@ -//! API requests. +//! Telegram API requests. mod has_payload; mod payload; diff --git a/src/requests/request.rs b/src/requests/request.rs index 6abb457e..0d6692d9 100644 --- a/src/requests/request.rs +++ b/src/requests/request.rs @@ -14,6 +14,8 @@ use crate::requests::{HasPayload, Output}; /// This is crucial for request wrappers which may want to cancel and/or never /// send the underlying request. E.g.: [`Throttle`]'s `send_ref` calls /// `B::send_ref` while _not_ meaning to really send the request right now. +/// +/// [`Throttle`]: crate::adaptors::Throttle #[cfg_attr(all(docsrs, feature = "nightly"), doc(spotlight))] pub trait Request: HasPayload { /* diff --git a/src/requests/requester.rs b/src/requests/requester.rs index 91f297af..4b31950b 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -4,7 +4,7 @@ use crate::{ types::ChatId, }; -/// The trait implemented by all bots & bot wrappers. +/// The trait implemented by all bots & bot adaptors. /// Essentially a request builder factory (?). /// /// _This trait is included in the crate's [`prelude`](crate::prelude)_. diff --git a/src/requests/requester_ext.rs b/src/requests/requester_ext.rs index 00b41b32..1b5530b2 100644 --- a/src/requests/requester_ext.rs +++ b/src/requests/requester_ext.rs @@ -1,10 +1,20 @@ -use crate::{ - adaptors::{CacheMe, throttle::Limits, Throttle, AutoSend}, - requests::Requester, -}; +use crate::requests::Requester; + +#[cfg(feature = "cache_me")] +use crate::adaptors::CacheMe; + +#[cfg(feature = "auto_send")] +use crate::adaptors::AutoSend; + +#[cfg(feature = "throttle")] +use crate::adaptors::throttle::{Limits, Throttle}; pub trait RequesterExt: Requester { /// Add `get_me` caching ability, see [`CacheMe`] for more. + /// + /// [`CacheMe`]: + #[cfg(feature = "cache_me")] + #[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "cache_me")))] fn cache_me(self) -> CacheMe where Self: Sized, @@ -13,6 +23,8 @@ pub trait RequesterExt: Requester { } /// Send requests automatically, see [`AutoSend`] for more. + #[cfg(feature = "auto_send")] + #[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "auto_send")))] fn auto_send(self) -> AutoSend where Self: Sized, @@ -23,6 +35,8 @@ pub trait RequesterExt: Requester { /// Add throttling ability, see [`Throttle`] for more. /// /// Note: this spawns the worker, just as [`Throttle::new_spawn`]. + #[cfg(feature = "throttle")] + #[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "throttle")))] fn throttle(self, limits: Limits) -> Throttle where Self: Sized, diff --git a/src/types/chat_member.rs b/src/types/chat_member.rs index 4d7c9530..46c5c7cf 100644 --- a/src/types/chat_member.rs +++ b/src/types/chat_member.rs @@ -27,14 +27,14 @@ pub enum ChatMemberKind { Kicked(Kicked), } -/// Creator of the group. This struct is part of the [`ChatmemberKind`] enum. +/// Creator of the group. This struct is part of the [`ChatMemberKind`] enum. #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct Creator { /// Custom title for this user. pub custom_title: Option, } -/// Administrator of the group. This struct is part of the [`ChatmemberKind`] +/// Administrator of the group. This struct is part of the [`ChatMemberKind`] /// enum. #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct Administrator { @@ -81,7 +81,7 @@ pub struct Administrator { pub can_promote_members: bool, } -/// User, restricted in the group. This struct is part of the [`ChatmemberKind`] +/// User, restricted in the group. This struct is part of the [`ChatMemberKind`] /// enum. #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct Restricted { @@ -106,7 +106,7 @@ pub struct Restricted { pub can_add_web_page_previews: bool, } -/// User kicked from the group. This struct is part of the [`ChatmemberKind`] +/// User kicked from the group. This struct is part of the [`ChatMemberKind`] /// enum. #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct Kicked { diff --git a/src/types/mod.rs b/src/types/mod.rs index e6f7e5fa..52bf56a2 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,4 +1,4 @@ -//! API types. +//! Telergam API types. pub use allowed_update::*; pub use animation::*; From 1eeac6d2da3334c0e0fcfd6915e93167895cf3bd Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 21 Oct 2020 19:45:03 +0300 Subject: [PATCH 130/755] update changelog --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7619e079..966b84ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `throttle`, `cache_me`, `auto_send` and `full` crate features +- `payloads` module +- `RequesterExt` trait which is implemented for all `Requester`s and allows easily wrapping them in adaptors +- `adaptors` module + - Request throttling - opt-in feature represented by `Throttle` bot adapter which allows automatically checking telegram limits ([#10][pr10]) + - Request auto sending - ability to `.await` requests without need to call `.send()` (opt-in feature represented by `AutoSend` bot adapter, [#8][pr8]) + - `get_me` caching (opt-in feature represented by `CacheMe` bot adapter) +- `Requester` trait which represents bot-clients ([#7][pr7]) +- `{Json,Multipart}Request` the `Bot` requests types ([#6][pr6]) +- `Output` alias to `<::Payload as Payload>::Output` +- `Payload`, `HasPayload` and `Request` traits which represent different parts of the request ([#5][pr5]) +- `GetUpdatesNonStrict` - fail proof version of `GetUpdates` - Move core code here from the [`teloxide`] main repo, for older changes see it's [`CHANGELOG.md`]. - Following modules were moved: - `bot` @@ -22,16 +34,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `GetUpdatesNonStrict` 'telegram' method, that behaves just like `GetUpdates` but doesn't fail if one of updates fails to be deserialized +[pr5]: https://github.com/teloxide/teloxide-core/pull/5 +[pr6]: https://github.com/teloxide/teloxide-core/pull/6 +[pr7]: https://github.com/teloxide/teloxide-core/pull/7 +[pr8]: https://github.com/teloxide/teloxide-core/pull/8 +[pr10]: https://github.com/teloxide/teloxide-core/pull/10 + ### Changed +- Merge `ApiErrorKind` and `KnownApiErrorKind` into `ApiError` ([#13][pr13]) +- Refactor ChatMember ([#9][pr9]) + - Replace a bunch of `Option<_>` fields with `ChatMemberKind` + - Remove setters (users are not expected to create this struct) + - Add getters - Changed internal mechanism of sending multipart requests - Added `RequestError::Io(io::Error)` to wrap I/O error those can happen while sending files to telegram - Change `StickerType`: instead of newtypes (`Png(InputFile)`) use structs (`Png { png_sticker: InputFile }`), add `StickerType::{png,tgs}` constructors - Make all fields of all methods `pub` +[pr9]: https://github.com/teloxide/teloxide-core/pull/9 +[pr13]: https://github.com/teloxide/teloxide-core/pull/13 + ### Removed +- `unstable-stream` feature (now `Bot::download_file_stream` is accesable by default) +- old `Request` trait - `RequestWithFile`, now multipart requests use `Request` - Remove all `#[non_exhaustive]` annotations From d47036012b65acc4e752f3598d79152ebce2dbb9 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 21 Oct 2020 19:51:00 +0300 Subject: [PATCH 131/755] fix rebase mistakes --- src/adaptors/throttle.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index 6d9fdd84..ad923fd4 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -348,7 +348,12 @@ impl Throttle { } } -impl Requester for Throttle { +impl Requester for Throttle +where + B::SendMessage: Send, +{ + type Err = B::Err; + type GetMe = B::GetMe; fn get_me(&self) -> Self::GetMe { @@ -414,8 +419,9 @@ impl HasPayload for ThrottlingRequest { } } -impl Request for ThrottlingRequest +impl Request for ThrottlingRequest where + R: Request + Send, ::Payload: GetChatId, { type Err = R::Err; @@ -619,7 +625,7 @@ mod chan_send { pub(super) struct ChanSend(#[pin] Inner); #[cfg(not(feature = "nightly"))] - type Inner = Pin)>>>>>; + type Inner = Pin)>>> + Send>>; #[cfg(feature = "nightly")] type Inner = impl Future)>>>; From 2dfe8a15c5d150c0a21977097b122554252d2b59 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 21 Oct 2020 19:53:39 +0300 Subject: [PATCH 132/755] fmt --- src/adaptors/throttle.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index ad923fd4..8a9481d4 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -349,11 +349,11 @@ impl Throttle { } impl Requester for Throttle -where - B::SendMessage: Send, -{ +where + B::SendMessage: Send, +{ type Err = B::Err; - + type GetMe = B::GetMe; fn get_me(&self) -> Self::GetMe { From d2b97b21206c2d731f9c3340533657b2c87a01d4 Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 22 Oct 2020 00:42:06 +0300 Subject: [PATCH 133/755] Use `#[serde(field_identifier)]` instead of hand-made workarounds This attribute doesn't seem to be documented btw... --- src/errors.rs | 845 +++++++++++++++++------------------ src/local_macros.rs | 82 ---- src/net/telegram_response.rs | 10 + 3 files changed, 430 insertions(+), 507 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index da2666b2..d12296ae 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -57,480 +57,475 @@ pub enum RequestError { Io(#[source] io::Error), } -serde_or_unknown! { - #[unknown_mod = api_error_internals] - #[unknown_kind = Kind] - #[unknown_path = "api_error_internals::Kind"] +/// A kind of an API error. +#[derive(Debug, Deserialize, PartialEq, Hash, Eq, Clone)] +#[serde(field_identifier)] +pub enum ApiError { + /// Occurs when the bot tries to send message to user who blocked the bot. + #[serde(rename = "Forbidden: bot was blocked by the user")] + BotBlocked, - /// A kind of an API error. - #[derive(Debug, Deserialize, PartialEq, Hash, Eq, Clone)] - pub enum ApiError { - #[unknown] - /// Error which is not known to `teloxide`. - /// - /// If you've received this error, please [open an issue] with the description of the error. - /// - /// [open an issue]: https://github.com/teloxide/teloxide/issues/new - Unknown(String), + /// Occurs when bot tries to modify a message without modification content. + /// + /// May happen in methods: + /// 1. [`EditMessageText`] + /// + /// [`EditMessageText`]: crate::requests::EditMessageText + #[serde(rename = "Bad Request: message is not modified: specified new message content and \ + reply markup are exactly the same as a current content and reply markup \ + of the message")] + MessageNotModified, - /// Occurs when the bot tries to send message to user who blocked the bot. - #[serde(rename = "Forbidden: bot was blocked by the user")] - BotBlocked, + /// Occurs when bot tries to forward or delete a message which was deleted. + /// + /// May happen in methods: + /// 1. [`ForwardMessage`] + /// 2. [`DeleteMessage`] + /// + /// [`ForwardMessage`]: crate::requests::ForwardMessage + /// [`DeleteMessage`]: crate::requests::DeleteMessage + #[serde(rename = "Bad Request: MESSAGE_ID_INVALID")] + MessageIdInvalid, - /// Occurs when bot tries to modify a message without modification content. - /// - /// May happen in methods: - /// 1. [`EditMessageText`] - /// - /// [`EditMessageText`]: crate::requests::EditMessageText - #[serde(rename = "Bad Request: message is not modified: specified new message content and \ - reply markup are exactly the same as a current content and reply markup \ - of the message")] - MessageNotModified, + /// Occurs when bot tries to forward a message which does not exists. + /// + /// May happen in methods: + /// 1. [`ForwardMessage`] + /// + /// [`ForwardMessage`]: crate::requests::ForwardMessage + #[serde(rename = "Bad Request: message to forward not found")] + MessageToForwardNotFound, - /// Occurs when bot tries to forward or delete a message which was deleted. - /// - /// May happen in methods: - /// 1. [`ForwardMessage`] - /// 2. [`DeleteMessage`] - /// - /// [`ForwardMessage`]: crate::requests::ForwardMessage - /// [`DeleteMessage`]: crate::requests::DeleteMessage - #[serde(rename = "Bad Request: MESSAGE_ID_INVALID")] - MessageIdInvalid, + /// Occurs when bot tries to delete a message which does not exists. + /// + /// May happen in methods: + /// 1. [`DeleteMessage`] + /// + /// [`DeleteMessage`]: crate::requests::DeleteMessage + #[serde(rename = "Bad Request: message to delete not found")] + MessageToDeleteNotFound, - /// Occurs when bot tries to forward a message which does not exists. - /// - /// May happen in methods: - /// 1. [`ForwardMessage`] - /// - /// [`ForwardMessage`]: crate::requests::ForwardMessage - #[serde(rename = "Bad Request: message to forward not found")] - MessageToForwardNotFound, + /// Occurs when bot tries to send a text message without text. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: message text is empty")] + MessageTextIsEmpty, - /// Occurs when bot tries to delete a message which does not exists. - /// - /// May happen in methods: - /// 1. [`DeleteMessage`] - /// - /// [`DeleteMessage`]: crate::requests::DeleteMessage - #[serde(rename = "Bad Request: message to delete not found")] - MessageToDeleteNotFound, + /// Occurs when bot tries to edit a message after long time. + /// + /// May happen in methods: + /// 1. [`EditMessageText`] + /// + /// [`EditMessageText`]: crate::requests::EditMessageText + #[serde(rename = "Bad Request: message can't be edited")] + MessageCantBeEdited, - /// Occurs when bot tries to send a text message without text. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: message text is empty")] - MessageTextIsEmpty, + /// Occurs when bot tries to delete a someone else's message in group where + /// it does not have enough rights. + /// + /// May happen in methods: + /// 1. [`DeleteMessage`] + /// + /// [`DeleteMessage`]: crate::requests::DeleteMessage + #[serde(rename = "Bad Request: message can't be deleted")] + MessageCantBeDeleted, - /// Occurs when bot tries to edit a message after long time. - /// - /// May happen in methods: - /// 1. [`EditMessageText`] - /// - /// [`EditMessageText`]: crate::requests::EditMessageText - #[serde(rename = "Bad Request: message can't be edited")] - MessageCantBeEdited, + /// Occurs when bot tries to edit a message which does not exists. + /// + /// May happen in methods: + /// 1. [`EditMessageText`] + /// + /// [`EditMessageText`]: crate::requests::EditMessageText + #[serde(rename = "Bad Request: message to edit not found")] + MessageToEditNotFound, - /// Occurs when bot tries to delete a someone else's message in group where - /// it does not have enough rights. - /// - /// May happen in methods: - /// 1. [`DeleteMessage`] - /// - /// [`DeleteMessage`]: crate::requests::DeleteMessage - #[serde(rename = "Bad Request: message can't be deleted")] - MessageCantBeDeleted, + /// Occurs when bot tries to reply to a message which does not exists. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: reply message not found")] + MessageToReplyNotFound, - /// Occurs when bot tries to edit a message which does not exists. - /// - /// May happen in methods: - /// 1. [`EditMessageText`] - /// - /// [`EditMessageText`]: crate::requests::EditMessageText - #[serde(rename = "Bad Request: message to edit not found")] - MessageToEditNotFound, + /// Occurs when bot tries to + #[serde(rename = "Bad Request: message identifier is not specified")] + MessageIdentifierNotSpecified, - /// Occurs when bot tries to reply to a message which does not exists. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: reply message not found")] - MessageToReplyNotFound, + /// Occurs when bot tries to send a message with text size greater then + /// 4096 symbols. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: message is too long")] + MessageIsTooLong, - /// Occurs when bot tries to - #[serde(rename = "Bad Request: message identifier is not specified")] - MessageIdentifierNotSpecified, + /// Occurs when bot tries to send media group with more than 10 items. + /// + /// May happen in methods: + /// 1. [`SendMediaGroup`] + /// + /// [`SendMediaGroup`]: crate::requests::SendMediaGroup + #[serde(rename = "Bad Request: Too much messages to send as an album")] + ToMuchMessages, - /// Occurs when bot tries to send a message with text size greater then - /// 4096 symbols. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: message is too long")] - MessageIsTooLong, + /// Occurs when bot tries to stop poll that has already been stopped. + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll has already been closed")] + PollHasAlreadyClosed, - /// Occurs when bot tries to send media group with more than 10 items. - /// - /// May happen in methods: - /// 1. [`SendMediaGroup`] - /// - /// [`SendMediaGroup`]: crate::requests::SendMediaGroup - #[serde(rename = "Bad Request: Too much messages to send as an album")] - ToMuchMessages, + /// Occurs when bot tries to send poll with less than 2 options. + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll must have at least 2 option")] + PollMustHaveMoreOptions, - /// Occurs when bot tries to stop poll that has already been stopped. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll has already been closed")] - PollHasAlreadyClosed, + /// Occurs when bot tries to send poll with more than 10 options. + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll can't have more than 10 options")] + PollCantHaveMoreOptions, - /// Occurs when bot tries to send poll with less than 2 options. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll must have at least 2 option")] - PollMustHaveMoreOptions, + /// Occurs when bot tries to send poll with empty option (without text). + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll options must be non-empty")] + PollOptionsMustBeNonEmpty, - /// Occurs when bot tries to send poll with more than 10 options. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll can't have more than 10 options")] - PollCantHaveMoreOptions, + /// Occurs when bot tries to send poll with empty question (without text). + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll question must be non-empty")] + PollQuestionMustBeNonEmpty, - /// Occurs when bot tries to send poll with empty option (without text). - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll options must be non-empty")] - PollOptionsMustBeNonEmpty, + /// Occurs when bot tries to send poll with total size of options more than + /// 100 symbols. + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll options length must not exceed 100")] + PollOptionsLengthTooLong, - /// Occurs when bot tries to send poll with empty question (without text). - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll question must be non-empty")] - PollQuestionMustBeNonEmpty, + /// Occurs when bot tries to send poll with question size more than 255 + /// symbols. + /// + /// May happen in methods: + /// 1. [`SendPoll`] + /// + /// [`SendPoll`]: crate::requests::SendPoll + #[serde(rename = "Bad Request: poll question length must not exceed 255")] + PollQuestionLengthTooLong, - /// Occurs when bot tries to send poll with total size of options more than - /// 100 symbols. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll options length must not exceed 100")] - PollOptionsLengthTooLong, + /// Occurs when bot tries to stop poll with message without poll. + /// + /// May happen in methods: + /// 1. [`StopPoll`] + /// + /// [`StopPoll`]: crate::requests::StopPoll + #[serde(rename = "Bad Request: message with poll to stop not found")] + MessageWithPollNotFound, - /// Occurs when bot tries to send poll with question size more than 255 - /// symbols. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll question length must not exceed 255")] - PollQuestionLengthTooLong, + /// Occurs when bot tries to stop poll with message without poll. + /// + /// May happen in methods: + /// 1. [`StopPoll`] + /// + /// [`StopPoll`]: crate::requests::StopPoll + #[serde(rename = "Bad Request: message is not a poll")] + MessageIsNotAPoll, - /// Occurs when bot tries to stop poll with message without poll. - /// - /// May happen in methods: - /// 1. [`StopPoll`] - /// - /// [`StopPoll`]: crate::requests::StopPoll - #[serde(rename = "Bad Request: message with poll to stop not found")] - MessageWithPollNotFound, + /// Occurs when bot tries to send a message to chat in which it is not a + /// member. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: chat not found")] + ChatNotFound, - /// Occurs when bot tries to stop poll with message without poll. - /// - /// May happen in methods: - /// 1. [`StopPoll`] - /// - /// [`StopPoll`]: crate::requests::StopPoll - #[serde(rename = "Bad Request: message is not a poll")] - MessageIsNotAPoll, + /// Occurs when bot tries to send method with unknown user_id. + /// + /// May happen in methods: + /// 1. [`getUserProfilePhotos`] + /// + /// [`getUserProfilePhotos`]: + /// crate::requests::GetUserProfilePhotos + #[serde(rename = "Bad Request: user not found")] + UserNotFound, - /// Occurs when bot tries to send a message to chat in which it is not a - /// member. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: chat not found")] - ChatNotFound, + /// Occurs when bot tries to send [`SetChatDescription`] with same text as + /// in the current description. + /// + /// May happen in methods: + /// 1. [`SetChatDescription`] + /// + /// [`SetChatDescription`]: crate::requests::SetChatDescription + #[serde(rename = "Bad Request: chat description is not modified")] + ChatDescriptionIsNotModified, - /// Occurs when bot tries to send method with unknown user_id. - /// - /// May happen in methods: - /// 1. [`getUserProfilePhotos`] - /// - /// [`getUserProfilePhotos`]: - /// crate::requests::GetUserProfilePhotos - #[serde(rename = "Bad Request: user not found")] - UserNotFound, + /// Occurs when bot tries to answer to query after timeout expire. + /// + /// May happen in methods: + /// 1. [`AnswerCallbackQuery`] + /// + /// [`AnswerCallbackQuery`]: crate::requests::AnswerCallbackQuery + #[serde(rename = "Bad Request: query is too old and response timeout expired or query id is \ + invalid")] + InvalidQueryID, - /// Occurs when bot tries to send [`SetChatDescription`] with same text as - /// in the current description. - /// - /// May happen in methods: - /// 1. [`SetChatDescription`] - /// - /// [`SetChatDescription`]: crate::requests::SetChatDescription - #[serde(rename = "Bad Request: chat description is not modified")] - ChatDescriptionIsNotModified, + /// Occurs when bot tries to send InlineKeyboardMarkup with invalid button + /// url. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: BUTTON_URL_INVALID")] + ButtonURLInvalid, - /// Occurs when bot tries to answer to query after timeout expire. - /// - /// May happen in methods: - /// 1. [`AnswerCallbackQuery`] - /// - /// [`AnswerCallbackQuery`]: crate::requests::AnswerCallbackQuery - #[serde(rename = "Bad Request: query is too old and response timeout expired or query id is \ - invalid")] - InvalidQueryID, + /// Occurs when bot tries to send button with data size more than 64 bytes. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: BUTTON_DATA_INVALID")] + ButtonDataInvalid, - /// Occurs when bot tries to send InlineKeyboardMarkup with invalid button - /// url. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: BUTTON_URL_INVALID")] - ButtonURLInvalid, + /// Occurs when bot tries to send button with data size == 0. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: can't parse inline keyboard button: Text buttons are \ + unallowed in the inline keyboard")] + TextButtonsAreUnallowed, - /// Occurs when bot tries to send button with data size more than 64 bytes. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: BUTTON_DATA_INVALID")] - ButtonDataInvalid, + /// Occurs when bot tries to get file by wrong file id. + /// + /// May happen in methods: + /// 1. [`GetFile`] + /// + /// [`GetFile`]: crate::requests::GetFile + #[serde(rename = "Bad Request: wrong file id")] + WrongFileID, - /// Occurs when bot tries to send button with data size == 0. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: can't parse inline keyboard button: Text buttons are \ - unallowed in the inline keyboard")] - TextButtonsAreUnallowed, + /// Occurs when bot tries to do some with group which was deactivated. + #[serde(rename = "Bad Request: group is deactivated")] + GroupDeactivated, - /// Occurs when bot tries to get file by wrong file id. - /// - /// May happen in methods: - /// 1. [`GetFile`] - /// - /// [`GetFile`]: crate::requests::GetFile - #[serde(rename = "Bad Request: wrong file id")] - WrongFileID, + /// Occurs when bot tries to set chat photo from file ID + /// + /// May happen in methods: + /// 1. [`SetChatPhoto`] + /// + /// [`SetChatPhoto`]: crate::requests::SetChatPhoto + #[serde(rename = "Bad Request: Photo should be uploaded as an InputFile")] + PhotoAsInputFileRequired, - /// Occurs when bot tries to do some with group which was deactivated. - #[serde(rename = "Bad Request: group is deactivated")] - GroupDeactivated, + /// Occurs when bot tries to add sticker to stickerset by invalid name. + /// + /// May happen in methods: + /// 1. [`AddStickerToSet`] + /// + /// [`AddStickerToSet`]: crate::requests::AddStickerToSet + #[serde(rename = "Bad Request: STICKERSET_INVALID")] + InvalidStickersSet, - /// Occurs when bot tries to set chat photo from file ID - /// - /// May happen in methods: - /// 1. [`SetChatPhoto`] - /// - /// [`SetChatPhoto`]: crate::requests::SetChatPhoto - #[serde(rename = "Bad Request: Photo should be uploaded as an InputFile")] - PhotoAsInputFileRequired, + /// Occurs when bot tries to pin a message without rights to pin in this + /// chat. + /// + /// May happen in methods: + /// 1. [`PinChatMessage`] + /// + /// [`PinChatMessage`]: crate::requests::PinChatMessage + #[serde(rename = "Bad Request: not enough rights to pin a message")] + NotEnoughRightsToPinMessage, - /// Occurs when bot tries to add sticker to stickerset by invalid name. - /// - /// May happen in methods: - /// 1. [`AddStickerToSet`] - /// - /// [`AddStickerToSet`]: crate::requests::AddStickerToSet - #[serde(rename = "Bad Request: STICKERSET_INVALID")] - InvalidStickersSet, + /// Occurs when bot tries to use method in group which is allowed only in a + /// supergroup or channel. + #[serde(rename = "Bad Request: method is available only for supergroups and channel")] + MethodNotAvailableInPrivateChats, - /// Occurs when bot tries to pin a message without rights to pin in this - /// chat. - /// - /// May happen in methods: - /// 1. [`PinChatMessage`] - /// - /// [`PinChatMessage`]: crate::requests::PinChatMessage - #[serde(rename = "Bad Request: not enough rights to pin a message")] - NotEnoughRightsToPinMessage, + /// Occurs when bot tries to demote chat creator. + /// + /// May happen in methods: + /// 1. [`PromoteChatMember`] + /// + /// [`PromoteChatMember`]: crate::requests::PromoteChatMember + #[serde(rename = "Bad Request: can't demote chat creator")] + CantDemoteChatCreator, - /// Occurs when bot tries to use method in group which is allowed only in a - /// supergroup or channel. - #[serde(rename = "Bad Request: method is available only for supergroups and channel")] - MethodNotAvailableInPrivateChats, + /// Occurs when bot tries to restrict self in group chats. + /// + /// May happen in methods: + /// 1. [`RestrictChatMember`] + /// + /// [`RestrictChatMember`]: crate::requests::RestrictChatMember + #[serde(rename = "Bad Request: can't restrict self")] + CantRestrictSelf, - /// Occurs when bot tries to demote chat creator. - /// - /// May happen in methods: - /// 1. [`PromoteChatMember`] - /// - /// [`PromoteChatMember`]: crate::requests::PromoteChatMember - #[serde(rename = "Bad Request: can't demote chat creator")] - CantDemoteChatCreator, + /// Occurs when bot tries to restrict chat member without rights to + /// restrict in this chat. + /// + /// May happen in methods: + /// 1. [`RestrictChatMember`] + /// + /// [`RestrictChatMember`]: crate::requests::RestrictChatMember + #[serde(rename = "Bad Request: not enough rights to restrict/unrestrict chat member")] + NotEnoughRightsToRestrict, - /// Occurs when bot tries to restrict self in group chats. - /// - /// May happen in methods: - /// 1. [`RestrictChatMember`] - /// - /// [`RestrictChatMember`]: crate::requests::RestrictChatMember - #[serde(rename = "Bad Request: can't restrict self")] - CantRestrictSelf, + /// Occurs when bot tries set webhook to protocol other than HTTPS. + /// + /// May happen in methods: + /// 1. [`SetWebhook`] + /// + /// [`SetWebhook`]: crate::requests::SetWebhook + #[serde(rename = "Bad Request: bad webhook: HTTPS url must be provided for webhook")] + WebhookRequireHTTPS, - /// Occurs when bot tries to restrict chat member without rights to - /// restrict in this chat. - /// - /// May happen in methods: - /// 1. [`RestrictChatMember`] - /// - /// [`RestrictChatMember`]: crate::requests::RestrictChatMember - #[serde(rename = "Bad Request: not enough rights to restrict/unrestrict chat member")] - NotEnoughRightsToRestrict, + /// Occurs when bot tries to set webhook to port other than 80, 88, 443 or + /// 8443. + /// + /// May happen in methods: + /// 1. [`SetWebhook`] + /// + /// [`SetWebhook`]: crate::requests::SetWebhook + #[serde(rename = "Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 \ + or 8443")] + BadWebhookPort, - /// Occurs when bot tries set webhook to protocol other than HTTPS. - /// - /// May happen in methods: - /// 1. [`SetWebhook`] - /// - /// [`SetWebhook`]: crate::requests::SetWebhook - #[serde(rename = "Bad Request: bad webhook: HTTPS url must be provided for webhook")] - WebhookRequireHTTPS, + /// Occurs when bot tries to set webhook to unknown host. + /// + /// May happen in methods: + /// 1. [`SetWebhook`] + /// + /// [`SetWebhook`]: crate::requests::SetWebhook + #[serde(rename = "Bad Request: bad webhook: Failed to resolve host: Name or service not known")] + UnknownHost, - /// Occurs when bot tries to set webhook to port other than 80, 88, 443 or - /// 8443. - /// - /// May happen in methods: - /// 1. [`SetWebhook`] - /// - /// [`SetWebhook`]: crate::requests::SetWebhook - #[serde(rename = "Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 \ - or 8443")] - BadWebhookPort, + /// Occurs when bot tries to set webhook to invalid URL. + /// + /// May happen in methods: + /// 1. [`SetWebhook`] + /// + /// [`SetWebhook`]: crate::requests::SetWebhook + #[serde(rename = "Bad Request: can't parse URL")] + CantParseUrl, - /// Occurs when bot tries to set webhook to unknown host. - /// - /// May happen in methods: - /// 1. [`SetWebhook`] - /// - /// [`SetWebhook`]: crate::requests::SetWebhook - #[serde(rename = "Bad Request: bad webhook: Failed to resolve host: Name or service not known")] - UnknownHost, + /// Occurs when bot tries to send message with unfinished entities. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: can't parse entities")] + CantParseEntities, - /// Occurs when bot tries to set webhook to invalid URL. - /// - /// May happen in methods: - /// 1. [`SetWebhook`] - /// - /// [`SetWebhook`]: crate::requests::SetWebhook - #[serde(rename = "Bad Request: can't parse URL")] - CantParseUrl, + /// Occurs when bot tries to use getUpdates while webhook is active. + /// + /// May happen in methods: + /// 1. [`GetUpdates`] + /// + /// [`GetUpdates`]: crate::requests::GetUpdates + #[serde(rename = "can't use getUpdates method while webhook is active")] + CantGetUpdates, - /// Occurs when bot tries to send message with unfinished entities. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: can't parse entities")] - CantParseEntities, + /// Occurs when bot tries to do some in group where bot was kicked. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Unauthorized: bot was kicked from a chat")] + BotKicked, - /// Occurs when bot tries to use getUpdates while webhook is active. - /// - /// May happen in methods: - /// 1. [`GetUpdates`] - /// - /// [`GetUpdates`]: crate::requests::GetUpdates - #[serde(rename = "can't use getUpdates method while webhook is active")] - CantGetUpdates, + /// Occurs when bot tries to send message to deactivated user. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Unauthorized: user is deactivated")] + UserDeactivated, - /// Occurs when bot tries to do some in group where bot was kicked. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Unauthorized: bot was kicked from a chat")] - BotKicked, + /// Occurs when you tries to initiate conversation with a user. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Unauthorized: bot can't initiate conversation with a user")] + CantInitiateConversation, - /// Occurs when bot tries to send message to deactivated user. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Unauthorized: user is deactivated")] - UserDeactivated, + /// Occurs when you tries to send message to bot. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Unauthorized: bot can't send messages to bots")] + CantTalkWithBots, - /// Occurs when you tries to initiate conversation with a user. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Unauthorized: bot can't initiate conversation with a user")] - CantInitiateConversation, + /// Occurs when bot tries to send button with invalid http url. + /// + /// May happen in methods: + /// 1. [`SendMessage`] + /// + /// [`SendMessage`]: crate::requests::SendMessage + #[serde(rename = "Bad Request: wrong HTTP URL")] + WrongHTTPurl, - /// Occurs when you tries to send message to bot. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Unauthorized: bot can't send messages to bots")] - CantTalkWithBots, + /// Occurs when bot tries GetUpdate before the timeout. Make sure that only + /// one Updater is running. + /// + /// May happen in methods: + /// 1. [`GetUpdates`] + /// + /// [`GetUpdates`]: crate::requests::GetUpdates + #[serde(rename = "Conflict: terminated by other getUpdates request; make sure that only one \ + bot instance is running")] + TerminatedByOtherGetUpdates, - /// Occurs when bot tries to send button with invalid http url. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: wrong HTTP URL")] - WrongHTTPurl, + /// Occurs when bot tries to get file by invalid file id. + /// + /// May happen in methods: + /// 1. [`GetFile`] + /// + /// [`GetFile`]: crate::requests::GetFile + #[serde(rename = "Bad Request: invalid file id")] + FileIdInvalid, - /// Occurs when bot tries GetUpdate before the timeout. Make sure that only - /// one Updater is running. - /// - /// May happen in methods: - /// 1. [`GetUpdates`] - /// - /// [`GetUpdates`]: crate::requests::GetUpdates - #[serde(rename = "Conflict: terminated by other getUpdates request; make sure that only one \ - bot instance is running")] - TerminatedByOtherGetUpdates, - - /// Occurs when bot tries to get file by invalid file id. - /// - /// May happen in methods: - /// 1. [`GetFile`] - /// - /// [`GetFile`]: crate::requests::GetFile - #[serde(rename = "Bad Request: invalid file id")] - FileIdInvalid, - } + /// Error which is not known to `teloxide`. + /// + /// If you've received this error, please [open an issue] with the + /// description of the error. + /// + /// [open an issue]: https://github.com/teloxide/teloxide/issues/new + Unknown(String), } diff --git a/src/local_macros.rs b/src/local_macros.rs index 85c421f6..5a48a207 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -106,85 +106,3 @@ macro_rules! req_future { }; } - -#[macro_use] -macro_rules! serde_or_unknown { - ( - #[unknown_mod = $mod_:ident] - #[unknown_kind = $Kind:ident] - #[unknown_path = $path:literal] - $( - #[ $($meta:tt)* ] - )* - $v:vis enum $Name:ident { - #[unknown] - $( - #[ $($unknown_meta:tt)* ] - )* - $Unknown:ident(String) - - $( - , - $( - #[ $($var_meta:tt)* ] - )* - $Var:ident - )* - $(,)? - } - ) => { - mod $mod_ { - #[allow(unused_imports)] - use super::{*, $Name as _}; - - $( - #[ $($meta)* ] - )* - $v enum $Name { - $( - $( - #[ $($var_meta)* ] - )* - $Var, - )* - } - - #[derive(::serde::Deserialize)] - #[serde(untagged)] - $v enum $Kind { - Known($Name), - Unknown(String), - } - - impl ::core::convert::From<$Kind> for super::$Name { - fn from(kind: $Kind) -> Self { - match kind { - $Kind::Unknown(string) => Self::Unknown(string), - $Kind::Known(known) => match known { - $( - $Name::$Var => Self::$Var, - )* - } - } - } - } - } - - $( - #[ $($meta)* ] - )* - #[serde(from = $path)] - $v enum $Name { - $( - $( - #[ $($var_meta)* ] - )* - $Var, - )* - $( - #[ $($unknown_meta)* ] - )* - $Unknown(String), - } - } -} diff --git a/src/net/telegram_response.rs b/src/net/telegram_response.rs index 4bf3849b..82fc73aa 100644 --- a/src/net/telegram_response.rs +++ b/src/net/telegram_response.rs @@ -66,4 +66,14 @@ mod tests { matches!(val, TelegramResponse::Err { error: ApiError::TerminatedByOtherGetUpdates, .. }) ); } + + #[test] + fn parse_unknown() { + let s = r#"{"ok":false,"error_code":111,"description":"Unknown description that won't match anything"}"#; + let val = serde_json::from_str::>(s).unwrap(); + + assert!( + matches!(val, TelegramResponse::Err { error: ApiError::Unknown(s), .. } if s == "Unknown description that won't match anything") + ); + } } From 69d503e6109a916754b49a342b72cbc83f534d72 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 23 Oct 2020 21:32:57 +0300 Subject: [PATCH 134/755] add internal impl_payload macro --- src/local_macros.rs | 201 +++++++++++++++++++++++++++++++++++ src/payloads/get_me.rs | 33 ++---- src/payloads/mod.rs | 3 +- src/payloads/send_message.rs | 138 ++++++------------------ 4 files changed, 245 insertions(+), 130 deletions(-) diff --git a/src/local_macros.rs b/src/local_macros.rs index 85c421f6..6cf4d543 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -107,6 +107,207 @@ macro_rules! req_future { }; } +/// Declares an item with a doc attribute computed by some macro expression. +/// This allows documentation to be dynamically generated based on input. +/// Necessary to work around https://github.com/rust-lang/rust/issues/52607. +#[macro_use] +macro_rules! calculated_doc { + ( + $( + #[doc = $doc:expr] + $thing:item + )* + ) => ( + $( + #[doc = $doc] + $thing + )* + ); +} + +/// Declare payload type, implement `Payload` trait amd ::new method for it, +/// declare setters trait and implement it for all type which have payload. +#[macro_use] +macro_rules! impl_payload { + ( + $( + #[ $($method_meta:tt)* ] + )* + $vi:vis $Method:ident ($Setters:ident) => $Ret:ty { + $( + required { + $( + $( + #[ $($field_meta:tt)* ] + )* + $v:vis $fields:ident : $FTy:ty $([$into:ident])? + , + )* + } + )? + + $( + optional { + $( + $( + #[ $($opt_field_meta:tt)* ] + )* + $opt_v:vis $opt_fields:ident : $OptFTy:ty $([$opt_into:ident])? + ),* + $(,)? + } + )? + } + ) => { + $( + #[ $($method_meta)* ] + )* + $vi struct $Method { + $( + $( + $( + #[ $($field_meta)* ] + )* + $v $fields : $FTy, + )* + )? + $( + $( + $( + #[ $($opt_field_meta)* ] + )* + $opt_v $opt_fields : core::option::Option<$OptFTy>, + )* + )? + } + + impl $Method { + $vi fn new($($($fields : impl_payload!(@into? $FTy $([$into])?)),*)?) -> Self { + Self { + $( + $( + $fields: $fields $(.$into())?, + )* + )? + $( + $( + $opt_fields: None, + )* + )? + } + } + } + + impl $crate::requests::Payload for $Method { + type Output = $Ret; + + const NAME: &'static str = stringify!($Method); + } + + calculated_doc! { + #[doc = concat!( + "Setters for fields of [`", + stringify!($Method), + "`]" + )] + $vi trait $Setters: $crate::requests::HasPayload + ::core::marker::Sized { + $( + $( + impl_payload! { @setter $Method $fields : $FTy $([$into])? } + )* + )? + $( + $( + impl_payload! { @setter_opt $Method $opt_fields : $OptFTy $([$opt_into])? } + )* + )? + } + } + + impl

$Setters for P where P: crate::requests::HasPayload {} + }; + (@setter_opt $Method:ident $field:ident : $FTy:ty [into]) => { + calculated_doc! { + #[doc = concat!( + "Setter for [`", + stringify!($field), + "`](", + stringify!($Method), + "::", + stringify!($field), + ") field." + )] + fn $field(mut self, value: T) -> Self + where + T: Into<$FTy>, + { + self.payload_mut().$field = Some(value.into()); + self + } + } + }; + (@setter_opt $Method:ident $field:ident : $FTy:ty) => { + calculated_doc! { + #[doc = concat!( + "Setter for [`", + stringify!($field), + "`](", + stringify!($Method), + "::", + stringify!($field), + ") field." + )] + fn $field(mut self, value: $FTy) -> Self { + self.payload_mut().$field = Some(value); + self + } + } + }; + (@setter $Method:ident $field:ident : $FTy:ty [into]) => { + calculated_doc! { + #[doc = concat!( + "Setter for [`", + stringify!($field), + "`](", + stringify!($Method), + "::", + stringify!($field), + ") field." + )] + fn $field(mut self, value: T) -> Self + where + T: Into<$FTy>, + { + self.payload_mut().$field = value.into(); + self + } + } + }; + (@setter $Method:ident $field:ident : $FTy:ty) => { + calculated_doc! { + #[doc = concat!( + "Setter for [`", + stringify!($field), + "`](", + stringify!($Method), + "::", + stringify!($field), + ") field." + )] + fn $field(mut self, value: $FTy) -> Self { + self.payload_mut().$field = value; + self + } + } + }; + (@into? $T:ty [into]) => { + impl ::core::convert::Into<$T> + }; + (@into? $T:ty) => { + $T + }; +} + #[macro_use] macro_rules! serde_or_unknown { ( diff --git a/src/payloads/get_me.rs b/src/payloads/get_me.rs index cabd6f22..4df0b815 100644 --- a/src/payloads/get_me.rs +++ b/src/payloads/get_me.rs @@ -1,29 +1,12 @@ use serde::{Deserialize, Serialize}; -use crate::{ - requests::{HasPayload, Payload}, - types::User, -}; +use crate::types::User; -/// A filter method for testing your bot's auth token. Requires no parameters. -/// Returns basic information about the bot in form of a [`User`] object. -/// -/// [`User`]: crate::types::User -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default, Deserialize, Serialize)] -pub struct GetMe {} - -impl GetMe { - pub const fn new() -> Self { - GetMe {} - } +impl_payload! { + /// A filter method for testing your bot's auth token. Requires no parameters. + /// Returns basic information about the bot in form of a [`User`] object. + /// + /// [`User`]: crate::types::User + #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default, Deserialize, Serialize)] + pub GetMe (GetMeSetters) => User {} } - -impl Payload for GetMe { - type Output = User; - - const NAME: &'static str = "getMe"; -} - -pub trait GetMeSetters: HasPayload + Sized {} - -impl

GetMeSetters for P where P: HasPayload {} diff --git a/src/payloads/mod.rs b/src/payloads/mod.rs index 22276002..33da224a 100644 --- a/src/payloads/mod.rs +++ b/src/payloads/mod.rs @@ -1,4 +1,5 @@ -/// Payloads - data types sended to relegram +//! Payloads - data types sended to relegram + pub mod setters; mod get_me; diff --git a/src/payloads/send_message.rs b/src/payloads/send_message.rs index c2ed2e00..5262fb0d 100644 --- a/src/payloads/send_message.rs +++ b/src/payloads/send_message.rs @@ -1,112 +1,42 @@ use serde::{Deserialize, Serialize}; -use crate::{ - requests::{HasPayload, Payload}, - types::{ChatId, Message, ParseMode, ReplyMarkup}, -}; +use crate::types::{ChatId, Message, ParseMode, ReplyMarkup}; -/// Use this method to send text messages. -/// -/// On success, the sent [`Message`] is returned. -/// -/// [`Message`]: crate::types::Message -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)] -pub struct SendMessage { - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`) - pub chat_id: ChatId, - /// Text of the message to be sent - pub text: String, - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in your bot's message. +impl_payload! { + /// Use this method to send text messages. /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub parse_mode: Option, - /// Disables link previews for links in this message - pub disable_web_page_preview: Option, - /// Sends the message silently. - /// Users will receive a notification with no sound. - pub disable_notification: Option, - /// If the message is a reply, [id] of the original message + /// On success, the sent [`Message`] is returned. /// - /// [id]: crate::types::Message::id - pub reply_to_message_id: Option, - /// Additional interface options. - pub reply_markup: Option, -} - -impl Payload for SendMessage { - type Output = Message; - - const NAME: &'static str = "sendMessage"; -} - -impl SendMessage { - pub fn new(chat_id: C, text: T) -> Self - where - C: Into, - T: Into, - { - SendMessage { - chat_id: chat_id.into(), - text: text.into(), - parse_mode: None, - disable_web_page_preview: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, + /// [`Message`]: crate::types::Message + #[serde_with_macros::skip_serializing_none] + #[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)] + pub SendMessage (SendMessageSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel + /// (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Text of the message to be sent + pub text: String [into], + } + optional { + /// Send [Markdown] or [HTML], if you want Telegram apps to show + /// [bold, italic, fixed-width text or inline URLs] in your bot's message. + /// + /// [Markdown]: crate::types::ParseMode::Markdown + /// [HTML]: crate::types::ParseMode::HTML + /// [bold, italic, fixed-width text or inline URLs]: crate::types::ParseMode + pub parse_mode: ParseMode, + /// Disables link previews for links in this message + pub disable_web_page_preview: bool, + /// Sends the message silently. + /// Users will receive a notification with no sound. + pub disable_notification: bool, + /// If the message is a reply, [id] of the original message + /// + /// [id]: crate::types::Message::id + pub reply_to_message_id: i32, + /// Additional interface options. + pub reply_markup: ReplyMarkup, } } } - -pub trait SendMessageSetters: HasPayload + Sized { - fn chat_id(mut self, value: T) -> Self - where - T: Into, - { - self.payload_mut().chat_id = value.into(); - self - } - - fn text(mut self, value: T) -> Self - where - T: Into, // TODO: into? - { - self.payload_mut().text = value.into(); - self - } - - fn parse_mode(mut self, value: ParseMode) -> Self { - self.payload_mut().parse_mode = Some(value); - self - } - - fn disable_web_page_preview(mut self, value: bool) -> Self { - self.payload_mut().disable_web_page_preview = Some(value); - self - } - - fn disable_notification(mut self, value: bool) -> Self { - self.payload_mut().disable_notification = Some(value); - self - } - - fn reply_to_message_id(mut self, value: i32) -> Self { - self.payload_mut().reply_to_message_id = Some(value); - self - } - - fn reply_markup(mut self, value: T) -> Self - where - T: Into, - { - self.payload_mut().reply_markup = Some(value.into()); - self - } -} - -impl

SendMessageSetters for P where P: HasPayload {} From fe9314f87ca0d65762ca0055bc59849f8128c3eb Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 24 Oct 2020 09:23:28 +0300 Subject: [PATCH 135/755] serde-rename `TelegramResponse::reponse` to `result` --- src/net/telegram_response.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/net/telegram_response.rs b/src/net/telegram_response.rs index 4bf3849b..46d5ff82 100644 --- a/src/net/telegram_response.rs +++ b/src/net/telegram_response.rs @@ -14,7 +14,7 @@ pub(crate) enum TelegramResponse { /// A dummy field. Used only for deserialization. #[allow(dead_code)] ok: True, - + #[serde(rename = "result")] response: R, }, Err { From 4b21e8ea6860de1200925217aaf218098a9054f9 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 24 Oct 2020 22:20:58 +0300 Subject: [PATCH 136/755] fmt --- src/local_macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/local_macros.rs b/src/local_macros.rs index ed5cd08f..756cbdf1 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -125,7 +125,7 @@ macro_rules! calculated_doc { ); } -/// Declare payload type, implement `Payload` trait amd ::new method for it, +/// Declare payload type, implement `Payload` trait amd ::new method for it, /// declare setters trait and implement it for all type which have payload. #[macro_use] macro_rules! impl_payload { From 6ae9db4edd85c141853021031c6d843a31320fbf Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 28 Oct 2020 19:05:28 +0300 Subject: [PATCH 137/755] make MessageDice::dice public --- src/types/message.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/message.rs b/src/types/message.rs index fb51b0ae..8a6890a1 100644 --- a/src/types/message.rs +++ b/src/types/message.rs @@ -1023,7 +1023,7 @@ impl MediaVenue { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageDice { /// Message is a dice with random value from 1 to 6. - dice: Dice, + pub dice: Dice, } mod getters { From 5ddbd50b37e2f1f63915dec3867be0b747d6ebf5 Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 2 Nov 2020 01:35:02 +0300 Subject: [PATCH 138/755] Implement IntoIterator-based convert for impl_payload! macro --- src/local_macros.rs | 68 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/src/local_macros.rs b/src/local_macros.rs index 756cbdf1..f5e6adee 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -140,7 +140,7 @@ macro_rules! impl_payload { $( #[ $($field_meta:tt)* ] )* - $v:vis $fields:ident : $FTy:ty $([$into:ident])? + $v:vis $fields:ident : $FTy:ty $([$conv:ident])? , )* } @@ -152,7 +152,7 @@ macro_rules! impl_payload { $( #[ $($opt_field_meta:tt)* ] )* - $opt_v:vis $opt_fields:ident : $OptFTy:ty $([$opt_into:ident])? + $opt_v:vis $opt_fields:ident : $OptFTy:ty $([$opt_conv:ident])? ),* $(,)? } @@ -182,11 +182,11 @@ macro_rules! impl_payload { } impl $Method { - $vi fn new($($($fields : impl_payload!(@into? $FTy $([$into])?)),*)?) -> Self { + $vi fn new($($($fields : impl_payload!(@convert? $FTy $([$conv])?)),*)?) -> Self { Self { $( $( - $fields: $fields $(.$into())?, + $fields: impl_payload!(@convert_map ($fields) $([$conv])?), )* )? $( @@ -213,12 +213,12 @@ macro_rules! impl_payload { $vi trait $Setters: $crate::requests::HasPayload + ::core::marker::Sized { $( $( - impl_payload! { @setter $Method $fields : $FTy $([$into])? } + impl_payload! { @setter $Method $fields : $FTy $([$conv])? } )* )? $( $( - impl_payload! { @setter_opt $Method $opt_fields : $OptFTy $([$opt_into])? } + impl_payload! { @setter_opt $Method $opt_fields : $OptFTy $([$opt_conv])? } )* )? } @@ -246,6 +246,26 @@ macro_rules! impl_payload { } } }; + (@setter_opt $Method:ident $field:ident : $FTy:ty [collect]) => { + calculated_doc! { + #[doc = concat!( + "Setter for [`", + stringify!($field), + "`](", + stringify!($Method), + "::", + stringify!($field), + ") field." + )] + fn $field(mut self, value: T) -> Self + where + T: ::core::iter::IntoIterator::Item>, + { + self.payload_mut().$field = Some(value.into_iter().collect()); + self + } + } + }; (@setter_opt $Method:ident $field:ident : $FTy:ty) => { calculated_doc! { #[doc = concat!( @@ -283,6 +303,26 @@ macro_rules! impl_payload { } } }; + (@setter $Method:ident $field:ident : $FTy:ty [collect]) => { + calculated_doc! { + #[doc = concat!( + "Setter for [`", + stringify!($field), + "`](", + stringify!($Method), + "::", + stringify!($field), + ") field." + )] + fn $field(mut self, value: T) -> Self + where + T: ::core::iter::IntoIterator::Item>, + { + self.payload_mut().$field = value.into_iter().collect(); + self + } + } + }; (@setter $Method:ident $field:ident : $FTy:ty) => { calculated_doc! { #[doc = concat!( @@ -300,10 +340,22 @@ macro_rules! impl_payload { } } }; - (@into? $T:ty [into]) => { + (@convert? $T:ty [into]) => { impl ::core::convert::Into<$T> }; - (@into? $T:ty) => { + (@convert? $T:ty [collect]) => { + impl ::core::iter::IntoIterator::Item> + }; + (@convert? $T:ty) => { $T }; + (@convert_map ($e:expr) [into]) => { + $e.into() + }; + (@convert_map ($e:expr) [collect]) => { + $e.into_iter().collect() + }; + (@convert_map ($e:expr)) => { + $e + }; } From 106c49fa464b06d21d1fe2fd707fac43e6fd1be7 Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 2 Nov 2020 02:21:02 +0300 Subject: [PATCH 139/755] add #[serde_with_macros::skip_serializing_none] annotation to the impl_payload output --- src/local_macros.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/local_macros.rs b/src/local_macros.rs index f5e6adee..98afd698 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -159,6 +159,7 @@ macro_rules! impl_payload { )? } ) => { + #[serde_with_macros::skip_serializing_none] $( #[ $($method_meta)* ] )* From a138d6824be34c0127f603f20da6d4743eceeda5 Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 2 Nov 2020 21:14:32 +0300 Subject: [PATCH 140/755] add #[must_use] attr to payloads implemented by macro --- src/local_macros.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/local_macros.rs b/src/local_macros.rs index 98afd698..1a60dcbf 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -160,6 +160,7 @@ macro_rules! impl_payload { } ) => { #[serde_with_macros::skip_serializing_none] + #[must_use = "Requests do nothing unless sent"] $( #[ $($method_meta)* ] )* From 9d887e51bbaff3420f9342d1f7274111cd30808b Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 4 Nov 2020 16:00:40 +0300 Subject: [PATCH 141/755] Rename `StickerType` - Rename `StickerType` => `InputSticker` - Rename `{CreateNewStickerSet,AddStickerToSet}::sticker_type}` => `sticker` This just makes more sense. --- CHANGELOG.md | 6 ++++++ src/bot/api.rs | 6 +++--- src/requests/all/add_sticker_to_set.rs | 8 ++++---- src/requests/all/create_new_sticker_set.rs | 12 ++++++------ src/types/sticker_type.rs | 9 +++++---- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 966b84ef..cc5baac2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Rename `StickerType` => `InputSticker`, `{CreateNewStickerSet,AddStickerToSet}::sticker_type}` => `sticker` ([#23][pr23]) +- Use `_: IntoIterator` bound instead of `_: Into>` in telegram methods which accept collections ([#21][pr21]) +- Make `MessageDice::dice` pub ([#20][pr20]) - Merge `ApiErrorKind` and `KnownApiErrorKind` into `ApiError` ([#13][pr13]) - Refactor ChatMember ([#9][pr9]) - Replace a bunch of `Option<_>` fields with `ChatMemberKind` @@ -55,6 +58,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [pr9]: https://github.com/teloxide/teloxide-core/pull/9 [pr13]: https://github.com/teloxide/teloxide-core/pull/13 +[pr20]: https://github.com/teloxide/teloxide-core/pull/20 +[pr21]: https://github.com/teloxide/teloxide-core/pull/21 +[pr23]: https://github.com/teloxide/teloxide-core/pull/23 ### Removed diff --git a/src/bot/api.rs b/src/bot/api.rs index e32bdbe9..621ae3f0 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -21,7 +21,7 @@ use crate::{ }, types::{ BotCommand, ChatId, ChatPermissions, InlineQueryResult, InputFile, InputMedia, - LabeledPrice, ParseMode, StickerType, TargetMessage, + LabeledPrice, ParseMode, InputSticker, TargetMessage, }, Bot, }; @@ -1366,7 +1366,7 @@ impl Bot { user_id: i32, name: N, title: T, - sticker_type: StickerType, + sticker_type: InputSticker, emojis: E, ) -> CreateNewStickerSet where @@ -1389,7 +1389,7 @@ impl Bot { &self, user_id: i32, name: N, - sticker_type: StickerType, + sticker_type: InputSticker, emojis: E, ) -> AddStickerToSet where diff --git a/src/requests/all/add_sticker_to_set.rs b/src/requests/all/add_sticker_to_set.rs index 31e68f68..a881e17e 100644 --- a/src/requests/all/add_sticker_to_set.rs +++ b/src/requests/all/add_sticker_to_set.rs @@ -8,7 +8,7 @@ use crate::{ use crate::{ requests::{RequestOld, ResponseResult}, - types::StickerType, + types::InputSticker, }; /// Use this method to add a new sticker to a set created by the bot. @@ -22,7 +22,7 @@ pub struct AddStickerToSet { pub user_id: i32, pub name: String, #[serde(flatten)] - pub sticker_type: StickerType, + pub sticker_type: InputSticker, pub emojis: String, pub mask_position: Option, } @@ -41,7 +41,7 @@ impl AddStickerToSet { bot: Bot, user_id: i32, name: N, - sticker_type: StickerType, + sticker_type: InputSticker, emojis: E, ) -> Self where @@ -73,7 +73,7 @@ impl AddStickerToSet { self } - pub fn sticker_type(mut self, val: StickerType) -> Self { + pub fn sticker_type(mut self, val: InputSticker) -> Self { self.sticker_type = val; self } diff --git a/src/requests/all/create_new_sticker_set.rs b/src/requests/all/create_new_sticker_set.rs index 85027a07..090c5b47 100644 --- a/src/requests/all/create_new_sticker_set.rs +++ b/src/requests/all/create_new_sticker_set.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::{ net, requests::{RequestOld, ResponseResult}, - types::{MaskPosition, StickerType, True}, + types::{MaskPosition, InputSticker, True}, Bot, }; @@ -20,7 +20,7 @@ pub struct CreateNewStickerSet { pub name: String, pub title: String, #[serde(flatten)] - pub sticker_type: StickerType, + pub sticker: InputSticker, pub emojis: String, pub contains_masks: Option, pub mask_position: Option, @@ -42,7 +42,7 @@ impl CreateNewStickerSet { user_id: i32, name: N, title: T, - sticker_type: StickerType, + sticker_type: InputSticker, emojis: E, ) -> Self where @@ -55,7 +55,7 @@ impl CreateNewStickerSet { user_id, name: name.into(), title: title.into(), - sticker_type, + sticker: sticker_type, emojis: emojis.into(), contains_masks: None, mask_position: None, @@ -91,8 +91,8 @@ impl CreateNewStickerSet { self } - pub fn sticker_type(mut self, val: StickerType) -> Self { - self.sticker_type = val; + pub fn sticker_type(mut self, val: InputSticker) -> Self { + self.sticker = val; self } diff --git a/src/types/sticker_type.rs b/src/types/sticker_type.rs index 1b6da91a..6a6647b0 100644 --- a/src/types/sticker_type.rs +++ b/src/types/sticker_type.rs @@ -2,9 +2,10 @@ use serde::Serialize; use crate::types::InputFile; +/// Sticker file that may be uploaded to telegram. #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)] #[serde(untagged)] -pub enum StickerType { +pub enum InputSticker { /// PNG image with the sticker, must be up to 512 kilobytes in size, /// dimensions must not exceed 512px, and either width or height must be /// exactly 512px. @@ -27,8 +28,8 @@ pub enum StickerType { Tgs { tgs_sticker: InputFile }, } -impl StickerType { - /// Create png-`StickerType`. +impl InputSticker { + /// Create png-`InputSticker`. /// /// See [`StickerType::Png`] for more /// @@ -37,7 +38,7 @@ impl StickerType { Self::Png { png_sticker } } - /// Create tgs-`StickerType`. + /// Create tgs-`InputSticker`. /// /// See [`StickerType::Tgs`] for more /// From a213459cc365898257c9756cea764a9e7e34240e Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 5 Nov 2020 03:22:23 +0300 Subject: [PATCH 142/755] fmt --- src/bot/api.rs | 2 +- src/requests/all/create_new_sticker_set.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bot/api.rs b/src/bot/api.rs index 621ae3f0..d831738e 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -21,7 +21,7 @@ use crate::{ }, types::{ BotCommand, ChatId, ChatPermissions, InlineQueryResult, InputFile, InputMedia, - LabeledPrice, ParseMode, InputSticker, TargetMessage, + InputSticker, LabeledPrice, ParseMode, TargetMessage, }, Bot, }; diff --git a/src/requests/all/create_new_sticker_set.rs b/src/requests/all/create_new_sticker_set.rs index 090c5b47..4d781bee 100644 --- a/src/requests/all/create_new_sticker_set.rs +++ b/src/requests/all/create_new_sticker_set.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::{ net, requests::{RequestOld, ResponseResult}, - types::{MaskPosition, InputSticker, True}, + types::{InputSticker, MaskPosition, True}, Bot, }; From 0deb42e16796dab473a9e3aae8669ba43446c99f Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 5 Nov 2020 03:25:32 +0300 Subject: [PATCH 143/755] fix docs for `InputSticker` --- src/types/sticker_type.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/types/sticker_type.rs b/src/types/sticker_type.rs index 6a6647b0..d15a4812 100644 --- a/src/types/sticker_type.rs +++ b/src/types/sticker_type.rs @@ -31,18 +31,18 @@ pub enum InputSticker { impl InputSticker { /// Create png-`InputSticker`. /// - /// See [`StickerType::Png`] for more + /// See [`InputSticker::Png`] for more /// - /// [`StickerType::Png`]: crate::types::StickerType::Png + /// [`InputSticker::Png`]: crate::types::InputSticker::Png pub fn png(png_sticker: InputFile) -> Self { Self::Png { png_sticker } } /// Create tgs-`InputSticker`. /// - /// See [`StickerType::Tgs`] for more + /// See [`InputSticker::Tgs`] for more /// - /// [`StickerType::Tgs`]: crate::types::StickerType::Tgs + /// [`InputSticker::Tgs`]: crate::types::InputSticker::Tgs pub fn tgs(tgs_sticker: InputFile) -> Self { Self::Tgs { tgs_sticker } } From 8f85cffe9496d33d7de1e6b625e86414e1c0f0c5 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 11 Nov 2020 22:41:44 +0300 Subject: [PATCH 144/755] Implement all payloads --- src/payloads/add_sticker_to_set.rs | 30 ++++ src/payloads/answer_callback_query.rs | 38 +++++ src/payloads/answer_inline_query.rs | 36 +++++ src/payloads/answer_pre_checkout_query.rs | 25 +++ src/payloads/answer_shipping_query.rs | 27 ++++ src/payloads/create_new_sticker_set.rs | 35 ++++ src/payloads/delete_chat_photo.rs | 17 ++ src/payloads/delete_chat_sticker_set.rs | 19 +++ src/payloads/delete_message.rs | 28 ++++ src/payloads/delete_sticker_from_set.rs | 17 ++ src/payloads/delete_webhook.rs | 16 ++ src/payloads/edit_message_caption.rs | 33 ++++ src/payloads/edit_message_caption_inline.rs | 31 ++++ src/payloads/edit_message_live_location.rs | 34 ++++ .../edit_message_live_location_inline.rs | 32 ++++ src/payloads/edit_message_media.rs | 29 ++++ src/payloads/edit_message_media_inline.rs | 27 ++++ src/payloads/edit_message_reply_markup.rs | 27 ++++ .../edit_message_reply_markup_inline.rs | 25 +++ src/payloads/edit_message_text.rs | 37 +++++ src/payloads/edit_message_text_inline.rs | 35 ++++ src/payloads/export_chat_invite_link.rs | 19 +++ src/payloads/forward_message.rs | 29 ++++ src/payloads/get_chat.rs | 19 +++ src/payloads/get_chat_administrators.rs | 19 +++ src/payloads/get_chat_member.rs | 21 +++ src/payloads/get_chat_members_count.rs | 17 ++ src/payloads/get_file.rs | 20 +++ src/payloads/get_me.rs | 14 +- src/payloads/get_my_commands.rs | 14 ++ src/payloads/get_sticker_set.rs | 17 ++ src/payloads/get_updates.rs | 33 ++++ src/payloads/get_user_profile_photos.rs | 25 +++ src/payloads/get_webhook_info.rs | 17 ++ src/payloads/kick_chat_member.rs | 25 +++ src/payloads/leave_chat.rs | 17 ++ src/payloads/mod.rs | 153 ++++++++++++++++++ src/payloads/pin_chat_message.rs | 23 +++ src/payloads/promote_chat_member.rs | 37 +++++ src/payloads/restrict_chat_member.rs | 25 +++ src/payloads/send_animation.rs | 52 ++++++ src/payloads/send_audio.rs | 55 +++++++ src/payloads/send_chat_action.rs | 33 ++++ src/payloads/send_contact.rs | 42 +++++ src/payloads/send_dice.rs | 34 ++++ src/payloads/send_document.rs | 46 ++++++ src/payloads/send_invoice.rs | 71 ++++++++ src/payloads/send_location.rs | 40 +++++ src/payloads/send_media_group.rs | 29 ++++ src/payloads/send_message.rs | 42 +++-- src/payloads/send_photo.rs | 42 +++++ src/payloads/send_poll.rs | 56 +++++++ src/payloads/send_sticker.rs | 36 +++++ src/payloads/send_venue.rs | 44 +++++ src/payloads/send_video.rs | 55 +++++++ src/payloads/send_video_note.rs | 45 ++++++ src/payloads/send_voice.rs | 46 ++++++ .../set_chat_administrator_custom_title.rs | 21 +++ src/payloads/set_chat_description.rs | 21 +++ src/payloads/set_chat_permissions.rs | 19 +++ src/payloads/set_chat_photo.rs | 19 +++ src/payloads/set_chat_sticker_set.rs | 19 +++ src/payloads/set_chat_title.rs | 19 +++ src/payloads/set_my_commands.rs | 17 ++ src/payloads/set_passport_data_errors.rs | 21 +++ src/payloads/set_sticker_position_in_set.rs | 19 +++ src/payloads/set_sticker_set_thumb.rs | 25 +++ src/payloads/set_webhook.rs | 35 ++++ src/payloads/setters.rs | 35 +++- src/payloads/stop_message_live_location.rs | 35 ++++ .../stop_message_live_location_inline.rs | 32 ++++ src/payloads/stop_poll.rs | 25 +++ src/payloads/unban_chat_member.rs | 19 +++ src/payloads/unpin_chat_message.rs | 17 ++ src/payloads/upload_sticker_file.rs | 21 +++ 75 files changed, 2301 insertions(+), 28 deletions(-) create mode 100644 src/payloads/add_sticker_to_set.rs create mode 100644 src/payloads/answer_callback_query.rs create mode 100644 src/payloads/answer_inline_query.rs create mode 100644 src/payloads/answer_pre_checkout_query.rs create mode 100644 src/payloads/answer_shipping_query.rs create mode 100644 src/payloads/create_new_sticker_set.rs create mode 100644 src/payloads/delete_chat_photo.rs create mode 100644 src/payloads/delete_chat_sticker_set.rs create mode 100644 src/payloads/delete_message.rs create mode 100644 src/payloads/delete_sticker_from_set.rs create mode 100644 src/payloads/delete_webhook.rs create mode 100644 src/payloads/edit_message_caption.rs create mode 100644 src/payloads/edit_message_caption_inline.rs create mode 100644 src/payloads/edit_message_live_location.rs create mode 100644 src/payloads/edit_message_live_location_inline.rs create mode 100644 src/payloads/edit_message_media.rs create mode 100644 src/payloads/edit_message_media_inline.rs create mode 100644 src/payloads/edit_message_reply_markup.rs create mode 100644 src/payloads/edit_message_reply_markup_inline.rs create mode 100644 src/payloads/edit_message_text.rs create mode 100644 src/payloads/edit_message_text_inline.rs create mode 100644 src/payloads/export_chat_invite_link.rs create mode 100644 src/payloads/forward_message.rs create mode 100644 src/payloads/get_chat.rs create mode 100644 src/payloads/get_chat_administrators.rs create mode 100644 src/payloads/get_chat_member.rs create mode 100644 src/payloads/get_chat_members_count.rs create mode 100644 src/payloads/get_file.rs create mode 100644 src/payloads/get_my_commands.rs create mode 100644 src/payloads/get_sticker_set.rs create mode 100644 src/payloads/get_updates.rs create mode 100644 src/payloads/get_user_profile_photos.rs create mode 100644 src/payloads/get_webhook_info.rs create mode 100644 src/payloads/kick_chat_member.rs create mode 100644 src/payloads/leave_chat.rs create mode 100644 src/payloads/pin_chat_message.rs create mode 100644 src/payloads/promote_chat_member.rs create mode 100644 src/payloads/restrict_chat_member.rs create mode 100644 src/payloads/send_animation.rs create mode 100644 src/payloads/send_audio.rs create mode 100644 src/payloads/send_chat_action.rs create mode 100644 src/payloads/send_contact.rs create mode 100644 src/payloads/send_dice.rs create mode 100644 src/payloads/send_document.rs create mode 100644 src/payloads/send_invoice.rs create mode 100644 src/payloads/send_location.rs create mode 100644 src/payloads/send_media_group.rs create mode 100644 src/payloads/send_photo.rs create mode 100644 src/payloads/send_poll.rs create mode 100644 src/payloads/send_sticker.rs create mode 100644 src/payloads/send_venue.rs create mode 100644 src/payloads/send_video.rs create mode 100644 src/payloads/send_video_note.rs create mode 100644 src/payloads/send_voice.rs create mode 100644 src/payloads/set_chat_administrator_custom_title.rs create mode 100644 src/payloads/set_chat_description.rs create mode 100644 src/payloads/set_chat_permissions.rs create mode 100644 src/payloads/set_chat_photo.rs create mode 100644 src/payloads/set_chat_sticker_set.rs create mode 100644 src/payloads/set_chat_title.rs create mode 100644 src/payloads/set_my_commands.rs create mode 100644 src/payloads/set_passport_data_errors.rs create mode 100644 src/payloads/set_sticker_position_in_set.rs create mode 100644 src/payloads/set_sticker_set_thumb.rs create mode 100644 src/payloads/set_webhook.rs create mode 100644 src/payloads/stop_message_live_location.rs create mode 100644 src/payloads/stop_message_live_location_inline.rs create mode 100644 src/payloads/stop_poll.rs create mode 100644 src/payloads/unban_chat_member.rs create mode 100644 src/payloads/unpin_chat_message.rs create mode 100644 src/payloads/upload_sticker_file.rs diff --git a/src/payloads/add_sticker_to_set.rs b/src/payloads/add_sticker_to_set.rs new file mode 100644 index 00000000..518c780e --- /dev/null +++ b/src/payloads/add_sticker_to_set.rs @@ -0,0 +1,30 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{InputSticker, MaskPosition, True}; + +impl_payload! { + /// Use this method to add a new sticker to a set created by the bot. Animated stickers can be added to animated sticker sets and only to them. Animated sticker sets can have up to 50 stickers. Static sticker sets can have up to 120 stickers. Returns _True_ on success. + #[derive(Debug, PartialEq, Clone, Serialize)] + pub AddStickerToSet (AddStickerToSetSetters) => True { + required { + /// User identifier of sticker file owner + pub user_id: u32, + /// Sticker set name + pub name: String [into], + /// **PNG** or **TGS** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a _file\_id_ as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. [More info on Sending Files Âģ] + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + #[serde(flatten)] + pub sticker: InputSticker, + /// One or more emoji corresponding to the sticker + pub emojis: String [into], + } + optional { + /// A JSON-serialized object for position where the mask should be placed on faces + pub mask_position: MaskPosition, + } + } +} diff --git a/src/payloads/answer_callback_query.rs b/src/payloads/answer_callback_query.rs new file mode 100644 index 00000000..10a9e487 --- /dev/null +++ b/src/payloads/answer_callback_query.rs @@ -0,0 +1,38 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::True; + +impl_payload! { + /// Use this method to send answers to callback queries sent from [inline keyboards]. The answer will be displayed to the user as a notification at the top of the chat screen or as an alert. On success, True is returned. + /// + /// >Alternatively, the user can be redirected to the specified Game URL. For this option to work, you must first create a game for your bot via [@Botfather] and accept the terms. Otherwise, you may use links like `t.me/your_bot?start=XXXX` that open your bot with a parameter. + /// + /// [inline keyboards]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [@Botfather]: https://t.me/botfather + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub AnswerCallbackQuery (AnswerCallbackQuerySetters) => True { + required { + /// Unique identifier for the query to be answered + pub callback_query_id: String [into], + } + optional { + /// Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters + pub text: String [into], + /// If true, an alert will be shown by the client instead of a notification at the top of the chat screen. Defaults to false. + pub show_alert: bool, + /// URL that will be opened by the user's client. If you have created a [`Game`] and accepted the conditions via [@Botfather], specify the URL that opens your game — note that this will only work if the query comes from a _[callback\_game]_ button. + /// + /// Otherwise, you may use links like `t.me/your\_bot?start=XXXX` that open your bot with a parameter. + /// + /// [@Botfather]: https://t.me/botfather + /// [`Game`]: crate::types::Game + /// [callback_game]: https://core.telegram.org/bots/api#inlinekeyboardbutton + pub url: String [into], + /// The maximum amount of time in seconds that the result of the callback query may be cached client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0. + pub cache_time: u32, + } + } +} diff --git a/src/payloads/answer_inline_query.rs b/src/payloads/answer_inline_query.rs new file mode 100644 index 00000000..f892959b --- /dev/null +++ b/src/payloads/answer_inline_query.rs @@ -0,0 +1,36 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{InlineQueryResult, True}; + +impl_payload! { + /// Use this method to send answers to an inline query. On success, _True_ is returned. No more than **50** results per query are allowed. + #[derive(Debug, PartialEq, Clone, Serialize)] + pub AnswerInlineQuery (AnswerInlineQuerySetters) => True { + required { + /// Unique identifier for the answered query + pub inline_query_id: String [into], + /// A JSON-serialized array of results for the inline query + pub results: Vec [collect], + } + optional { + /// The maximum amount of time in seconds that the result of the inline query may be cached on the server. Defaults to 300. + pub cache_time: u32, + /// Pass _True_, if results may be cached on the server side only for the user that sent the query. By default, results may be returned to any user who sends the same query + pub is_personal: bool, + /// Pass the offset that a client should send in the next query with the same text to receive more results. Pass an empty string if there are no more results or if you don't support pagination. Offset length can't exceed 64 bytes. + pub next_offset: String [into], + /// If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter switch_pm_parameter + pub switch_pm_text: String [into], + /// [Deep-linking] parameter for the /start message sent to the bot when user presses the switch button. 1-64 characters, only `A-Z`, `a-z`, `0-9`, `_` and `-` are allowed. + /// + /// _Example_: An inline bot that sends YouTube videos can ask the user to connect the bot to their YouTube account to adapt search results accordingly. To do this, it displays a 'Connect your YouTube account' button above the results, or even before showing any. The user presses the button, switches to a private chat with the bot and, in doing so, passes a start parameter that instructs the bot to return an oauth link. Once done, the bot can offer a [switch_inline] button so that the user can easily return to the chat where they wanted to use the bot's inline capabilities. + /// + /// [Deep-linking]: https://core.telegram.org/bots#deep-linking + /// [switch_inline]: https://core.telegram.org/bots/api#inlinekeyboardmarkup + pub switch_pm_parameter: String [into], + } + } +} diff --git a/src/payloads/answer_pre_checkout_query.rs b/src/payloads/answer_pre_checkout_query.rs new file mode 100644 index 00000000..8cfbd7d5 --- /dev/null +++ b/src/payloads/answer_pre_checkout_query.rs @@ -0,0 +1,25 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::True; + +impl_payload! { + /// 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 pre\_checkout\_query. Use this method to respond to such pre-checkout queries. On success, True is returned. **Note:** The Bot API must receive an answer within 10 seconds after the pre-checkout query was sent. + /// + /// [`Update`]: crate::types::Update + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub AnswerPreCheckoutQuery (AnswerPreCheckoutQuerySetters) => True { + required { + /// Unique identifier for the query to be answered + pub pre_checkout_query_id: String [into], + /// 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. + pub ok: bool, + } + optional { + /// Required if ok is False. Error message in human readable form that explains the reason for failure to proceed with the checkout (e.g. "Sorry, somebody just bought the last of our amazing black T-shirts while you were busy filling out your payment details. Please choose a different color or garment!"). Telegram will display this message to the user. + pub error_message: String [into], + } + } +} diff --git a/src/payloads/answer_shipping_query.rs b/src/payloads/answer_shipping_query.rs new file mode 100644 index 00000000..471b35b9 --- /dev/null +++ b/src/payloads/answer_shipping_query.rs @@ -0,0 +1,27 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ShippingOption, True}; + +impl_payload! { + /// 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 queries. On success, True is returned. + /// + /// [`Update`]: crate::types::Update + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub AnswerShippingQuery (AnswerShippingQuerySetters) => True { + required { + /// Unique identifier for the query to be answered + pub shipping_query_id: String [into], + /// Specify True if delivery to the specified address is possible and False if there are any problems (for example, if delivery to the specified address is not possible) + pub ok: bool, + } + optional { + /// Required if ok is True. A JSON-serialized array of available shipping options. + pub shipping_options: Vec [collect], + /// Required if ok is False. Error message in human readable form that explains why it is impossible to complete the order (e.g. 'Sorry, delivery to your desired address is unavailable'). Telegram will display this message to the user. + pub error_message: String [into], + } + } +} diff --git a/src/payloads/create_new_sticker_set.rs b/src/payloads/create_new_sticker_set.rs new file mode 100644 index 00000000..cbeb51a8 --- /dev/null +++ b/src/payloads/create_new_sticker_set.rs @@ -0,0 +1,35 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{InputFile, MaskPosition, True}; + +impl_payload! { + /// Use this method to create a new sticker set owned by a user. The bot will be able to edit the sticker set thus created. You must use exactly one of the fields _png\_sticker_ or _tgs\_sticker_. Returns _True_ on success. + #[derive(Debug, PartialEq, Clone, Serialize)] + pub CreateNewStickerSet (CreateNewStickerSetSetters) => True { + required { + /// User identifier of sticker file owner + pub user_id: u32, + /// Short name of sticker set, to be used in `t.me/addstickers/` URLs (e.g., _animals_). Can contain only english letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in _“\_by\_”. _ is case insensitive. 1-64 characters. + pub name: String [into], + /// Sticker set title, 1-64 characters + pub title: String [into], + /// One or more emoji corresponding to the sticker + pub emojis: String [into], + } + optional { + /// **PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a _file\_id_ as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. [More info on Sending Files Âģ] + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + pub png_sticker: InputFile, + /// **TGS** animation with the sticker, uploaded using multipart/form-data. See https://core.telegram.org/animated_stickers#technical-requirements for technical requirements + pub tgs_sticker: InputFile, + /// Pass _True_, if a set of mask stickers should be created + pub contains_masks: bool, + /// A JSON-serialized object for position where the mask should be placed on faces + pub mask_position: MaskPosition, + } + } +} diff --git a/src/payloads/delete_chat_photo.rs b/src/payloads/delete_chat_photo.rs new file mode 100644 index 00000000..2bd8a6cc --- /dev/null +++ b/src/payloads/delete_chat_photo.rs @@ -0,0 +1,17 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::ChatId; + +impl_payload! { + /// 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 must have the appropriate admin rights. Returns True on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub DeleteChatPhoto (DeleteChatPhotoSetters) => String { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + } + } +} diff --git a/src/payloads/delete_chat_sticker_set.rs b/src/payloads/delete_chat_sticker_set.rs new file mode 100644 index 00000000..e5ca6230 --- /dev/null +++ b/src/payloads/delete_chat_sticker_set.rs @@ -0,0 +1,19 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, True}; + +impl_payload! { + /// Use this method to delete a group sticker set from a supergroup. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Use the field `can_set_sticker_set` optionally returned in [`GetChat`] requests to check if the bot can use this method. Returns _True_ on success. + /// + /// [`GetChat`]: crate::payloads::GetChat + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub DeleteChatStickerSet (DeleteChatStickerSetSetters) => True { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + } + } +} diff --git a/src/payloads/delete_message.rs b/src/payloads/delete_message.rs new file mode 100644 index 00000000..039bcb38 --- /dev/null +++ b/src/payloads/delete_message.rs @@ -0,0 +1,28 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, True}; + +impl_payload! { + /// Use this method to delete a message, including service messages, with the following limitations: + /// - A message can only be deleted if it was sent less than 48 hours ago. + /// - A dice message in a private chat can only be deleted if it was sent more than 24 hours ago. + /// - Bots can delete outgoing messages in private chats, groups, and supergroups. + /// - Bots can delete incoming messages in private chats. + /// - Bots granted can_post_messages permissions can delete outgoing messages in channels. + /// - If the bot is an administrator of a group, it can delete any message there. + /// - If the bot has can_delete_messages permission in a supergroup or a channel, it can delete any message there. + /// + /// Returns True on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub DeleteMessage (DeleteMessageSetters) => True { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`). + pub chat_id: ChatId [into], + /// Identifier of the message to delete + pub message_id: i64, + } + } +} diff --git a/src/payloads/delete_sticker_from_set.rs b/src/payloads/delete_sticker_from_set.rs new file mode 100644 index 00000000..2b6396aa --- /dev/null +++ b/src/payloads/delete_sticker_from_set.rs @@ -0,0 +1,17 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::True; + +impl_payload! { + /// Use this method to delete a sticker from a set created by the bot. Returns _True_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub DeleteStickerFromSet (DeleteStickerFromSetSetters) => True { + required { + /// File identifier of the sticker + pub sticker: String [into], + } + } +} diff --git a/src/payloads/delete_webhook.rs b/src/payloads/delete_webhook.rs new file mode 100644 index 00000000..ae4907c4 --- /dev/null +++ b/src/payloads/delete_webhook.rs @@ -0,0 +1,16 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::True; + +impl_payload! { + /// Use this method to remove webhook integration if you decide to switch back to [`GetUpdates`]. Returns True on success. Requires no parameters. + /// + /// [`GetUpdates`]: crate::payloads::GetUpdates + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub DeleteWebhook (DeleteWebhookSetters) => True { + + } +} diff --git a/src/payloads/edit_message_caption.rs b/src/payloads/edit_message_caption.rs new file mode 100644 index 00000000..725986b9 --- /dev/null +++ b/src/payloads/edit_message_caption.rs @@ -0,0 +1,33 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, InlineKeyboardMarkup, Message, ParseMode}; + +impl_payload! { + /// Use this method to edit captions of messages. On success, the edited Message is returned. + /// + /// See also: [`EditMessageCaptionInline`](crate::payloads::EditMessageCaptionInline) + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub EditMessageCaption (EditMessageCaptionSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`). + pub chat_id: ChatId [into], + /// Identifier of the message to edit + pub message_id: i64, + /// New caption of the message, 0-1024 characters after entities parsing + pub caption: String [into], + } + optional { + /// Mode for parsing entities in the message text. See [formatting options] for more details. + /// + /// [formatting options]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: ParseMode, + /// A JSON-serialized object for an [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: InlineKeyboardMarkup, + } + } +} diff --git a/src/payloads/edit_message_caption_inline.rs b/src/payloads/edit_message_caption_inline.rs new file mode 100644 index 00000000..de4b1ebb --- /dev/null +++ b/src/payloads/edit_message_caption_inline.rs @@ -0,0 +1,31 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{InlineKeyboardMarkup, ParseMode, True}; + +impl_payload! { + /// Use this method to edit captions of messages. On success, _True_ is returned. + /// + /// See also: [`EditMessageCaption`](crate::payloads::EditMessageCaption) + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub EditMessageCaptionInline (EditMessageCaptionInlineSetters) => True { + required { + /// Identifier of the inline message + pub inline_message_id: String [into], + /// New caption of the message, 0-1024 characters after entities parsing + pub caption: String [into], + } + optional { + /// Mode for parsing entities in the message text. See [formatting options] for more details. + /// + /// [formatting options]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: ParseMode, + /// A JSON-serialized object for an [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: InlineKeyboardMarkup, + } + } +} diff --git a/src/payloads/edit_message_live_location.rs b/src/payloads/edit_message_live_location.rs new file mode 100644 index 00000000..8d22ea0b --- /dev/null +++ b/src/payloads/edit_message_live_location.rs @@ -0,0 +1,34 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, Message, ReplyMarkup}; + +impl_payload! { + /// Use this method to edit live location messages. A location can be edited until its live_period expires or editing is explicitly disabled by a call to [`StopMessageLiveLocation`]. On success, the edited Message is returned. + /// + /// See also: [`EditMessageLiveLocationInline`](crate::payloads::EditMessageLiveLocationInline) + /// + /// [`StopMessageLiveLocation`]: crate::payloads::StopMessageLiveLocation + #[derive(Debug, PartialEq, Clone, Serialize)] + pub EditMessageLiveLocation (EditMessageLiveLocationSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Identifier of the message to edit + pub message_id: i64, + /// Latitude of new location + pub latitude: f64, + /// Longitude of new location + pub longitude: f64, + } + optional { + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: ReplyMarkup [into], + } + } +} diff --git a/src/payloads/edit_message_live_location_inline.rs b/src/payloads/edit_message_live_location_inline.rs new file mode 100644 index 00000000..cf26c12a --- /dev/null +++ b/src/payloads/edit_message_live_location_inline.rs @@ -0,0 +1,32 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{Message, ReplyMarkup}; + +impl_payload! { + /// Use this method to edit live location messages. A location can be edited until its live_period expires or editing is explicitly disabled by a call to [`StopMessageLiveLocation`]. On success, True is returned. + /// + /// See also: [`EditMessageLiveLocation`](crate::payloads::EditMessageLiveLocation) + /// + /// [`StopMessageLiveLocation`]: crate::payloads::StopMessageLiveLocation + #[derive(Debug, PartialEq, Clone, Serialize)] + pub EditMessageLiveLocationInline (EditMessageLiveLocationInlineSetters) => Message { + required { + /// Identifier of the inline message + pub inline_message_id: String [into], + /// Latitude of new location + pub latitude: f64, + /// Longitude of new location + pub longitude: f64, + } + optional { + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub reply_markup: ReplyMarkup [into], + } + } +} diff --git a/src/payloads/edit_message_media.rs b/src/payloads/edit_message_media.rs new file mode 100644 index 00000000..9fda6118 --- /dev/null +++ b/src/payloads/edit_message_media.rs @@ -0,0 +1,29 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, InlineKeyboardMarkup, InputMedia, Message}; + +impl_payload! { + /// Use this method to edit animation, audio, document, photo, or video messages. If a message is a part of a message album, then it can be edited only to a photo or a video. Otherwise, message type can be changed arbitrarily. When inline message is edited, new file can't be uploaded. Use previously uploaded file via its file_id or specify a URL. On success, the edited Message is returned. + /// + /// See also: [`EditMessageMediaInline`](crate::payloads::EditMessageMediaInline) + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub EditMessageMedia (EditMessageMediaSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`). + pub chat_id: ChatId [into], + /// Identifier of the message to edit + pub message_id: i64, + /// A JSON-serialized object for a new media content of the message + pub media: InputMedia, + } + optional { + /// A JSON-serialized object for an [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: InlineKeyboardMarkup, + } + } +} diff --git a/src/payloads/edit_message_media_inline.rs b/src/payloads/edit_message_media_inline.rs new file mode 100644 index 00000000..bdc4fd53 --- /dev/null +++ b/src/payloads/edit_message_media_inline.rs @@ -0,0 +1,27 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{InlineKeyboardMarkup, InputMedia, True}; + +impl_payload! { + /// Use this method to edit animation, audio, document, photo, or video messages. If a message is a part of a message album, then it can be edited only to a photo or a video. Otherwise, message type can be changed arbitrarily. When inline message is edited, new file can't be uploaded. Use previously uploaded file via its file_id or specify a URL. On success, _True_ is returned. + /// + /// See also: [`EditMessageMedia`](crate::payloads::EditMessageMedia) + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub EditMessageMediaInline (EditMessageMediaInlineSetters) => True { + required { + /// Identifier of the inline message + pub inline_message_id: String [into], + /// A JSON-serialized object for a new media content of the message + pub media: InputMedia, + } + optional { + /// A JSON-serialized object for an [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: InlineKeyboardMarkup, + } + } +} diff --git a/src/payloads/edit_message_reply_markup.rs b/src/payloads/edit_message_reply_markup.rs new file mode 100644 index 00000000..2103165e --- /dev/null +++ b/src/payloads/edit_message_reply_markup.rs @@ -0,0 +1,27 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, InlineKeyboardMarkup, Message}; + +impl_payload! { + /// Use this method to edit only the reply markup of messages. On success, the edited Message is returned. + /// + /// See also: [`EditMessageMediaInline`](crate::payloads::EditMessageMediaInline) + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub EditMessageReplyMarkup (EditMessageReplyMarkupSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`). + pub chat_id: ChatId [into], + /// Identifier of the message to edit + pub message_id: i64, + } + optional { + /// A JSON-serialized object for an [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: InlineKeyboardMarkup, + } + } +} diff --git a/src/payloads/edit_message_reply_markup_inline.rs b/src/payloads/edit_message_reply_markup_inline.rs new file mode 100644 index 00000000..439b85dc --- /dev/null +++ b/src/payloads/edit_message_reply_markup_inline.rs @@ -0,0 +1,25 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{InlineKeyboardMarkup, True}; + +impl_payload! { + /// Use this method to edit only the reply markup of messages. On success, _True_ is returned. + /// + /// See also: [`EditMessageReplyMarkup`](crate::payloads::EditMessageReplyMarkup) + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub EditMessageReplyMarkupInline (EditMessageReplyMarkupInlineSetters) => True { + required { + /// Identifier of the inline message + pub inline_message_id: String [into], + } + optional { + /// A JSON-serialized object for an [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: InlineKeyboardMarkup, + } + } +} diff --git a/src/payloads/edit_message_text.rs b/src/payloads/edit_message_text.rs new file mode 100644 index 00000000..94a1b7d4 --- /dev/null +++ b/src/payloads/edit_message_text.rs @@ -0,0 +1,37 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, InlineKeyboardMarkup, Message, ParseMode}; + +impl_payload! { + /// Use this method to edit text and [games] messages. On success, the edited Message is returned. + /// + /// See also: [`EditMessageTextInline`](crate::payloads::EditMessageTextInline) + /// + /// [games]: https://core.telegram.org/bots/api#games + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub EditMessageText (EditMessageTextSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`). + pub chat_id: ChatId [into], + /// Identifier of the message to edit + pub message_id: i64, + /// New text of the message, 1-4096 characters after entities parsing + pub text: String [into], + } + optional { + /// Mode for parsing entities in the message text. See [formatting options] for more details. + /// + /// [formatting options]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: ParseMode, + /// Disables link previews for links in this message + pub disable_web_page_preview: bool, + /// A JSON-serialized object for an [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: InlineKeyboardMarkup, + } + } +} diff --git a/src/payloads/edit_message_text_inline.rs b/src/payloads/edit_message_text_inline.rs new file mode 100644 index 00000000..33837594 --- /dev/null +++ b/src/payloads/edit_message_text_inline.rs @@ -0,0 +1,35 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{InlineKeyboardMarkup, ParseMode, True}; + +impl_payload! { + /// Use this method to edit text and [games] messages. On success, _True_ is returned. + /// + /// See also: [`EditMessageText`](crate::payloads::EditMessageText) + /// + /// [games]: https://core.telegram.org/bots/api#games + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub EditMessageTextInline (EditMessageTextInlineSetters) => True { + required { + /// Identifier of the inline message + pub inline_message_id: String [into], + /// New text of the message, 1-4096 characters after entities parsing + pub text: String [into], + } + optional { + /// Mode for parsing entities in the message text. See [formatting options] for more details. + /// + /// [formatting options]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: ParseMode, + /// Disables link previews for links in this message + pub disable_web_page_preview: bool, + /// A JSON-serialized object for an [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: InlineKeyboardMarkup, + } + } +} diff --git a/src/payloads/export_chat_invite_link.rs b/src/payloads/export_chat_invite_link.rs new file mode 100644 index 00000000..8ced7840 --- /dev/null +++ b/src/payloads/export_chat_invite_link.rs @@ -0,0 +1,19 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::ChatId; + +impl_payload! { + /// Use this method to generate a new invite link for a chat; any previously generated link is revoked. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Returns the new invite link as String on success. + /// + /// > Note: Each administrator in a chat generates their own invite links. Bots can't use invite links generated by other administrators. If you want your bot to work with invite links, it will need to generate its own link using exportChatInviteLink — after this the link will become available to the bot via the getChat method. If your bot needs to generate a new invite link replacing its previous one, use exportChatInviteLink again. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub ExportChatInviteLink (ExportChatInviteLinkSetters) => String { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + } + } +} diff --git a/src/payloads/forward_message.rs b/src/payloads/forward_message.rs new file mode 100644 index 00000000..70fc8a19 --- /dev/null +++ b/src/payloads/forward_message.rs @@ -0,0 +1,29 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, Message}; + +impl_payload! { + /// Use this method to forward messages of any kind. On success, the sent [`Message`] is returned. + /// + /// [`Message`]: crate::types::Message + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub ForwardMessage (ForwardMessageSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Unique identifier for the chat where the original message was sent (or channel username in the format `@channelusername`) + pub from_chat_id: ChatId [into], + /// Message identifier in the chat specified in _from\_chat\_id_ + pub message_id: i64, + } + optional { + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + } + } +} diff --git a/src/payloads/get_chat.rs b/src/payloads/get_chat.rs new file mode 100644 index 00000000..cfc13aab --- /dev/null +++ b/src/payloads/get_chat.rs @@ -0,0 +1,19 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{Chat, ChatId}; + +impl_payload! { + /// 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 or channel, etc.). Returns a [`Chat`] object on success. + /// + /// [`Chat`]: crate::types::Chat + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub GetChat (GetChatSetters) => Chat { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + } + } +} diff --git a/src/payloads/get_chat_administrators.rs b/src/payloads/get_chat_administrators.rs new file mode 100644 index 00000000..c0eae482 --- /dev/null +++ b/src/payloads/get_chat_administrators.rs @@ -0,0 +1,19 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, ChatMember}; + +impl_payload! { + /// Use this method to get a list of administrators in a chat. On success, returns an Array of [`ChatMember`] objects that contains information about all chat administrators except other bots. If the chat is a group or a supergroup and no administrators were appointed, only the creator will be returned. + /// + /// [`ChatMember`]: crate::types::ChatMember + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub GetChatAdministrators (GetChatAdministratorsSetters) => ChatMember { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + } + } +} diff --git a/src/payloads/get_chat_member.rs b/src/payloads/get_chat_member.rs new file mode 100644 index 00000000..008681c3 --- /dev/null +++ b/src/payloads/get_chat_member.rs @@ -0,0 +1,21 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, ChatMember}; + +impl_payload! { + /// Use this method to get information about a member of a chat. Returns a [`ChatMember`] object on success. + /// + /// [`ChatMember`]: crate::types::ChatMember + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub GetChatMember (GetChatMemberSetters) => ChatMember { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Unique identifier of the target user + pub user_id: u32, + } + } +} diff --git a/src/payloads/get_chat_members_count.rs b/src/payloads/get_chat_members_count.rs new file mode 100644 index 00000000..3223c5fa --- /dev/null +++ b/src/payloads/get_chat_members_count.rs @@ -0,0 +1,17 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::ChatId; + +impl_payload! { + /// Use this method to get the number of members in a chat. Returns _Int_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub GetChatMembersCount (GetChatMembersCountSetters) => u32 { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + } + } +} diff --git a/src/payloads/get_file.rs b/src/payloads/get_file.rs new file mode 100644 index 00000000..3037bbe6 --- /dev/null +++ b/src/payloads/get_file.rs @@ -0,0 +1,20 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::File; + +impl_payload! { + /// Use this method to get basic info about a file and prepare it for downloading. For the moment, bots can download files of up to 20MB in size. On success, a [`File`] object is returned. The file can then be downloaded via the link `https://api.telegram.org/file/bot/`, where `` is taken from the response. 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 [`GetFile`] again. + /// + /// [`GetFile`]: crate::payloads::GetFile + /// [`File`]: crate::types::File + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub GetFile (GetFileSetters) => File { + required { + /// File identifier to get info about + pub file_id: String [into], + } + } +} diff --git a/src/payloads/get_me.rs b/src/payloads/get_me.rs index 4df0b815..eef1103e 100644 --- a/src/payloads/get_me.rs +++ b/src/payloads/get_me.rs @@ -1,12 +1,16 @@ -use serde::{Deserialize, Serialize}; +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; use crate::types::User; impl_payload! { - /// A filter method for testing your bot's auth token. Requires no parameters. - /// Returns basic information about the bot in form of a [`User`] object. + /// A simple method for testing your bot's auth token. Requires no parameters. Returns basic information about the bot in form of a [`User`] object. /// /// [`User`]: crate::types::User - #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default, Deserialize, Serialize)] - pub GetMe (GetMeSetters) => User {} + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub GetMe (GetMeSetters) => User { + + } } diff --git a/src/payloads/get_my_commands.rs b/src/payloads/get_my_commands.rs new file mode 100644 index 00000000..a7ffb195 --- /dev/null +++ b/src/payloads/get_my_commands.rs @@ -0,0 +1,14 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +impl_payload! { + /// Use this method to get the current list of the bot's commands. Requires no parameters. Returns Array of [`BotCommand`] on success. + /// + /// [`BotCommand`]: crate::types::BotCommand + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub GetMyCommands (GetMyCommandsSetters) => u32 { + + } +} diff --git a/src/payloads/get_sticker_set.rs b/src/payloads/get_sticker_set.rs new file mode 100644 index 00000000..e0b061a1 --- /dev/null +++ b/src/payloads/get_sticker_set.rs @@ -0,0 +1,17 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::True; + +impl_payload! { + /// Use this method to get a sticker set. On success, a StickerSet object is returned. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub GetStickerSet (GetStickerSetSetters) => True { + required { + /// Name of the sticker set + pub name: String [into], + } + } +} diff --git a/src/payloads/get_updates.rs b/src/payloads/get_updates.rs new file mode 100644 index 00000000..d7484c8e --- /dev/null +++ b/src/payloads/get_updates.rs @@ -0,0 +1,33 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{AllowedUpdate, Update}; + +impl_payload! { + /// Use this method to receive incoming updates using long polling ([wiki]). An Array of [`Update`] objects is returned. + /// + /// [`Update`]: crate::types::Update + /// [wiki]: https://en.wikipedia.org/wiki/Push_technology#Long_polling + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub GetUpdates (GetUpdatesSetters) => Vec { + + optional { + /// Identifier of the first update to be returned. Must be greater by one than the highest among the identifiers of previously received updates. By default, updates starting with the earliest unconfirmed update are returned. An update is considered confirmed as soon as [`GetUpdates`] is called with an offset higher than its update_id. The negative offset can be specified to retrieve updates starting from -offset update from the end of the updates queue. All previous updates will forgotten. + /// + /// [`GetUpdates`]: crate::payloads::GetUpdates + pub offset: i64, + /// Limits the number of updates to be retrieved. Values between 1-100 are accepted. Defaults to 100. + pub limit: u8, + /// Timeout in seconds for long polling. Defaults to 0, i.e. usual short polling. Should be positive, short polling should be used for testing purposes only. + pub timeout: u32, + /// A JSON-serialized list of the update types you want your bot to receive. For example, specify [“message”, “edited_channel_post”, “callback_query”] to only receive updates of these types. See [`Update`] for a complete list of available update types. Specify an empty list to receive all updates regardless of type (default). If not specified, the previous setting will be used. + /// + /// Please note that this parameter doesn't affect updates created before the call to the getUpdates, so unwanted updates may be received for a short period of time. + /// + /// [`Update`]: crate::types::Update + pub allowed_updates: Vec [collect], + } + } +} diff --git a/src/payloads/get_user_profile_photos.rs b/src/payloads/get_user_profile_photos.rs new file mode 100644 index 00000000..ac95ee08 --- /dev/null +++ b/src/payloads/get_user_profile_photos.rs @@ -0,0 +1,25 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::UserProfilePhotos; + +impl_payload! { + /// Use this method to get a list of profile pictures for a user. Returns a [`UserProfilePhotos`] object. + /// + /// [`UserProfilePhotos`]: crate::types::UserProfilePhotos + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub GetUserProfilePhotos (GetUserProfilePhotosSetters) => UserProfilePhotos { + required { + /// Unique identifier of the target user + pub user_id: u32, + } + optional { + /// Sequential number of the first photo to be returned. By default, all photos are returned. + pub offset: u32, + /// Limits the number of photos to be retrieved. Values between 1-100 are accepted. Defaults to 100. + pub limit: u8, + } + } +} diff --git a/src/payloads/get_webhook_info.rs b/src/payloads/get_webhook_info.rs new file mode 100644 index 00000000..9430960d --- /dev/null +++ b/src/payloads/get_webhook_info.rs @@ -0,0 +1,17 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::WebhookInfo; + +impl_payload! { + /// Use this method to get current webhook status. Requires no parameters. On success, returns a [`WebhookInfo`] object. If the bot is using [`GetUpdates`], will return an object with the _url_ field empty. + /// + /// [`GetUpdates`]: crate::payloads::GetUpdates + /// [`WebhookInfo`]: crate::types::WebhookInfo + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub GetWebhookInfo (GetWebhookInfoSetters) => WebhookInfo { + + } +} diff --git a/src/payloads/kick_chat_member.rs b/src/payloads/kick_chat_member.rs new file mode 100644 index 00000000..decbc8bf --- /dev/null +++ b/src/payloads/kick_chat_member.rs @@ -0,0 +1,25 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, True}; + +impl_payload! { + /// Use this method to kick a user from a group, a supergroup or a channel. In the case of supergroups and channels, the user will not be able to return to the group on their own using invite links, etc., unless [unbanned] first. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Returns _True_ on success. + /// + /// [unbanned]: crate::payloads::UnbanChatMember + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub KickChatMember (KickChatMemberSetters) => True { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Unique identifier of the target user + pub user_id: u32, + } + optional { + /// Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever + pub until_date: u64, + } + } +} diff --git a/src/payloads/leave_chat.rs b/src/payloads/leave_chat.rs new file mode 100644 index 00000000..e3c05bad --- /dev/null +++ b/src/payloads/leave_chat.rs @@ -0,0 +1,17 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::ChatId; + +impl_payload! { + /// Use this method for your bot to leave a group, supergroup or channel. Returns _True_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub LeaveChat (LeaveChatSetters) => String { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + } + } +} diff --git a/src/payloads/mod.rs b/src/payloads/mod.rs index 33da224a..2316d16e 100644 --- a/src/payloads/mod.rs +++ b/src/payloads/mod.rs @@ -2,8 +2,161 @@ pub mod setters; +// This block is auto generated by `cg` (878e847). +// **DO NOT EDIT THIS BLOCK**, +// edit `cg` instead. +mod add_sticker_to_set; +mod answer_callback_query; +mod answer_inline_query; +mod answer_pre_checkout_query; +mod answer_shipping_query; +mod create_new_sticker_set; +mod delete_chat_photo; +mod delete_chat_sticker_set; +mod delete_message; +mod delete_sticker_from_set; +mod delete_webhook; +mod edit_message_caption; +mod edit_message_caption_inline; +mod edit_message_live_location; +mod edit_message_live_location_inline; +mod edit_message_media; +mod edit_message_media_inline; +mod edit_message_reply_markup; +mod edit_message_reply_markup_inline; +mod edit_message_text; +mod edit_message_text_inline; +mod export_chat_invite_link; +mod forward_message; +mod get_chat; +mod get_chat_administrators; +mod get_chat_member; +mod get_chat_members_count; +mod get_file; mod get_me; +mod get_my_commands; +mod get_sticker_set; +mod get_updates; +mod get_user_profile_photos; +mod get_webhook_info; +mod kick_chat_member; +mod leave_chat; +mod pin_chat_message; +mod promote_chat_member; +mod restrict_chat_member; +mod send_animation; +mod send_audio; +mod send_chat_action; +mod send_contact; +mod send_dice; +mod send_document; +mod send_invoice; +mod send_location; +mod send_media_group; mod send_message; +mod send_photo; +mod send_poll; +mod send_sticker; +mod send_venue; +mod send_video; +mod send_video_note; +mod send_voice; +mod set_chat_administrator_custom_title; +mod set_chat_description; +mod set_chat_permissions; +mod set_chat_photo; +mod set_chat_sticker_set; +mod set_chat_title; +mod set_my_commands; +mod set_passport_data_errors; +mod set_sticker_position_in_set; +mod set_sticker_set_thumb; +mod set_webhook; +mod stop_message_live_location; +mod stop_message_live_location_inline; +mod stop_poll; +mod unban_chat_member; +mod unpin_chat_message; +mod upload_sticker_file; +pub use add_sticker_to_set::{AddStickerToSet, AddStickerToSetSetters}; +pub use answer_callback_query::{AnswerCallbackQuery, AnswerCallbackQuerySetters}; +pub use answer_inline_query::{AnswerInlineQuery, AnswerInlineQuerySetters}; +pub use answer_pre_checkout_query::{AnswerPreCheckoutQuery, AnswerPreCheckoutQuerySetters}; +pub use answer_shipping_query::{AnswerShippingQuery, AnswerShippingQuerySetters}; +pub use create_new_sticker_set::{CreateNewStickerSet, CreateNewStickerSetSetters}; +pub use delete_chat_photo::{DeleteChatPhoto, DeleteChatPhotoSetters}; +pub use delete_chat_sticker_set::{DeleteChatStickerSet, DeleteChatStickerSetSetters}; +pub use delete_message::{DeleteMessage, DeleteMessageSetters}; +pub use delete_sticker_from_set::{DeleteStickerFromSet, DeleteStickerFromSetSetters}; +pub use delete_webhook::{DeleteWebhook, DeleteWebhookSetters}; +pub use edit_message_caption::{EditMessageCaption, EditMessageCaptionSetters}; +pub use edit_message_caption_inline::{EditMessageCaptionInline, EditMessageCaptionInlineSetters}; +pub use edit_message_live_location::{EditMessageLiveLocation, EditMessageLiveLocationSetters}; +pub use edit_message_live_location_inline::{ + EditMessageLiveLocationInline, EditMessageLiveLocationInlineSetters, +}; +pub use edit_message_media::{EditMessageMedia, EditMessageMediaSetters}; +pub use edit_message_media_inline::{EditMessageMediaInline, EditMessageMediaInlineSetters}; +pub use edit_message_reply_markup::{EditMessageReplyMarkup, EditMessageReplyMarkupSetters}; +pub use edit_message_reply_markup_inline::{ + EditMessageReplyMarkupInline, EditMessageReplyMarkupInlineSetters, +}; +pub use edit_message_text::{EditMessageText, EditMessageTextSetters}; +pub use edit_message_text_inline::{EditMessageTextInline, EditMessageTextInlineSetters}; +pub use export_chat_invite_link::{ExportChatInviteLink, ExportChatInviteLinkSetters}; +pub use forward_message::{ForwardMessage, ForwardMessageSetters}; +pub use get_chat::{GetChat, GetChatSetters}; +pub use get_chat_administrators::{GetChatAdministrators, GetChatAdministratorsSetters}; +pub use get_chat_member::{GetChatMember, GetChatMemberSetters}; +pub use get_chat_members_count::{GetChatMembersCount, GetChatMembersCountSetters}; +pub use get_file::{GetFile, GetFileSetters}; pub use get_me::{GetMe, GetMeSetters}; +pub use get_my_commands::{GetMyCommands, GetMyCommandsSetters}; +pub use get_sticker_set::{GetStickerSet, GetStickerSetSetters}; +pub use get_updates::{GetUpdates, GetUpdatesSetters}; +pub use get_user_profile_photos::{GetUserProfilePhotos, GetUserProfilePhotosSetters}; +pub use get_webhook_info::{GetWebhookInfo, GetWebhookInfoSetters}; +pub use kick_chat_member::{KickChatMember, KickChatMemberSetters}; +pub use leave_chat::{LeaveChat, LeaveChatSetters}; +pub use pin_chat_message::{PinChatMessage, PinChatMessageSetters}; +pub use promote_chat_member::{PromoteChatMember, PromoteChatMemberSetters}; +pub use restrict_chat_member::{RestrictChatMember, RestrictChatMemberSetters}; +pub use send_animation::{SendAnimation, SendAnimationSetters}; +pub use send_audio::{SendAudio, SendAudioSetters}; +pub use send_chat_action::{SendChatAction, SendChatActionSetters}; +pub use send_contact::{SendContact, SendContactSetters}; +pub use send_dice::{SendDice, SendDiceSetters}; +pub use send_document::{SendDocument, SendDocumentSetters}; +pub use send_invoice::{SendInvoice, SendInvoiceSetters}; +pub use send_location::{SendLocation, SendLocationSetters}; +pub use send_media_group::{SendMediaGroup, SendMediaGroupSetters}; pub use send_message::{SendMessage, SendMessageSetters}; +pub use send_photo::{SendPhoto, SendPhotoSetters}; +pub use send_poll::{SendPoll, SendPollSetters}; +pub use send_sticker::{SendSticker, SendStickerSetters}; +pub use send_venue::{SendVenue, SendVenueSetters}; +pub use send_video::{SendVideo, SendVideoSetters}; +pub use send_video_note::{SendVideoNote, SendVideoNoteSetters}; +pub use send_voice::{SendVoice, SendVoiceSetters}; +pub use set_chat_administrator_custom_title::{ + SetChatAdministratorCustomTitle, SetChatAdministratorCustomTitleSetters, +}; +pub use set_chat_description::{SetChatDescription, SetChatDescriptionSetters}; +pub use set_chat_permissions::{SetChatPermissions, SetChatPermissionsSetters}; +pub use set_chat_photo::{SetChatPhoto, SetChatPhotoSetters}; +pub use set_chat_sticker_set::{SetChatStickerSet, SetChatStickerSetSetters}; +pub use set_chat_title::{SetChatTitle, SetChatTitleSetters}; +pub use set_my_commands::{SetMyCommands, SetMyCommandsSetters}; +pub use set_passport_data_errors::{SetPassportDataErrors, SetPassportDataErrorsSetters}; +pub use set_sticker_position_in_set::{SetStickerPositionInSet, SetStickerPositionInSetSetters}; +pub use set_sticker_set_thumb::{SetStickerSetThumb, SetStickerSetThumbSetters}; +pub use set_webhook::{SetWebhook, SetWebhookSetters}; +pub use stop_message_live_location::{StopMessageLiveLocation, StopMessageLiveLocationSetters}; +pub use stop_message_live_location_inline::{ + StopMessageLiveLocationInline, StopMessageLiveLocationInlineSetters, +}; +pub use stop_poll::{StopPoll, StopPollSetters}; +pub use unban_chat_member::{UnbanChatMember, UnbanChatMemberSetters}; +pub use unpin_chat_message::{UnpinChatMessage, UnpinChatMessageSetters}; +pub use upload_sticker_file::{UploadStickerFile, UploadStickerFileSetters}; diff --git a/src/payloads/pin_chat_message.rs b/src/payloads/pin_chat_message.rs new file mode 100644 index 00000000..43aff985 --- /dev/null +++ b/src/payloads/pin_chat_message.rs @@ -0,0 +1,23 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::ChatId; + +impl_payload! { + /// Use this method to pin a message in a group, a supergroup, or a channel. The bot must be an administrator in the chat for this to work and must have the 'can_pin_messages' admin right in the supergroup or 'can_edit_messages' admin right in the channel. Returns _True_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub PinChatMessage (PinChatMessageSetters) => String { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Identifier of a message to pin + pub message_id: i64, + } + optional { + /// Pass True, if it is not necessary to send a notification to all chat members about the new pinned message. Notifications are always disabled in channels. + pub disable_notification: bool, + } + } +} diff --git a/src/payloads/promote_chat_member.rs b/src/payloads/promote_chat_member.rs new file mode 100644 index 00000000..f79afc4e --- /dev/null +++ b/src/payloads/promote_chat_member.rs @@ -0,0 +1,37 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, True}; + +impl_payload! { + /// Use this method to promote or demote a user in a supergroup or a channel. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Pass _False_ for all boolean parameters to demote a user. Returns _True_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub PromoteChatMember (PromoteChatMemberSetters) => True { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Unique identifier of the target user + pub user_id: u32, + } + optional { + /// Pass True, if the administrator can change chat title, photo and other settings + pub can_change_info: bool, + /// Pass True, if the administrator can create channel posts, channels only + pub can_post_messages: bool, + /// Pass True, if the administrator can edit messages of other users and can pin messages, channels only + pub can_edit_messages: bool, + /// Pass True, if the administrator can delete messages of other users + pub can_delete_messages: bool, + /// Pass True, if the administrator can invite new users to the chat + pub can_invite_users: bool, + /// Pass True, if the administrator can restrict, ban or unban chat members + pub can_restrict_members: bool, + /// Pass True, if the administrator can pin messages, supergroups only + pub can_pin_messages: bool, + /// Pass True, if the administrator can add new administrators with a subset of their own privileges or demote administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed by him) + pub can_promote_members: bool, + } + } +} diff --git a/src/payloads/restrict_chat_member.rs b/src/payloads/restrict_chat_member.rs new file mode 100644 index 00000000..3cce3797 --- /dev/null +++ b/src/payloads/restrict_chat_member.rs @@ -0,0 +1,25 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, ChatPermissions, True}; + +impl_payload! { + /// Use this method to restrict a user in a supergroup. The bot must be an administrator in the supergroup for this to work and must have the appropriate admin rights. Pass _True_ for all permissions to lift restrictions from a user. Returns _True_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub RestrictChatMember (RestrictChatMemberSetters) => True { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Unique identifier of the target user + pub user_id: u32, + /// A JSON-serialized object for new user permissions + pub permissions: ChatPermissions, + } + optional { + /// Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever + pub until_date: u64, + } + } +} diff --git a/src/payloads/send_animation.rs b/src/payloads/send_animation.rs new file mode 100644 index 00000000..81741c9b --- /dev/null +++ b/src/payloads/send_animation.rs @@ -0,0 +1,52 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}; + +impl_payload! { + /// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound). On success, the sent [`Message`] is returned. Bots can currently send animation files of up to 50 MB in size, this limit may be changed in the future. + /// + /// [`Message`]: crate::types::Message + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SendAnimation (SendAnimationSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Animation to send. Pass a file_id as String to send a video that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a video from the Internet, or upload a new video using multipart/form-data. [More info on Sending Files Âģ] + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + pub animation: InputFile, + /// Animation caption (may also be used when resending videos by _file\_id_), 0-1024 characters after entities parsing + pub caption: String [into], + } + optional { + /// Duration of the animation in seconds + pub duration: u32, + /// Animation width + pub width: u32, + /// Animation height + pub height: u32, + /// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under . [More info on Sending Files Âģ] + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + pub thumb: InputFile, + /// Mode for parsing entities in the animation caption. See [formatting options] for more details. + /// + /// [formatting options]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: ParseMode, + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// If the message is a reply, ID of the original message + pub reply_to_message_id: i64, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: ReplyMarkup [into], + } + } +} diff --git a/src/payloads/send_audio.rs b/src/payloads/send_audio.rs new file mode 100644 index 00000000..2992df9b --- /dev/null +++ b/src/payloads/send_audio.rs @@ -0,0 +1,55 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}; + +impl_payload! { + /// Use this method to send audio files, if you want Telegram clients to display them in the music player. Your audio must be in the .MP3 or .M4A format. On success, the sent [`Message`] is returned. Bots can currently send audio files of up to 50 MB in size, this limit may be changed in the future. + /// + /// For sending voice messages, use the [`SendVoice`] method instead. + /// + /// [`SendVoice`]: crate::payloads::SendVoice + /// [`Message`]: crate::types::Message + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SendAudio (SendAudioSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Audio file to send. Pass a file_id as String to send an audio file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an audio file from the Internet, or upload a new one using multipart/form-data. [More info on Sending Files Âģ] + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + pub audio: InputFile, + /// Audio caption, 0-1024 characters after entities parsing + pub caption: String [into], + } + optional { + /// Mode for parsing entities in the audio caption. See [formatting options] for more details. + /// + /// [formatting options]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: ParseMode, + /// Duration of the audio in seconds + pub duration: u32, + /// Performer + pub performer: String [into], + /// Track name + pub title: String [into], + /// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under . [More info on Sending Files Âģ] + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + pub thumb: InputFile, + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// If the message is a reply, ID of the original message + pub reply_to_message_id: i64, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub reply_markup: ReplyMarkup [into], + } + } +} diff --git a/src/payloads/send_chat_action.rs b/src/payloads/send_chat_action.rs new file mode 100644 index 00000000..9d012a7e --- /dev/null +++ b/src/payloads/send_chat_action.rs @@ -0,0 +1,33 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatAction, ChatId, Message}; + +impl_payload! { + /// Use this method when you need to tell the user that something is happening on the bot's side. The status is set for 5 seconds or less (when a message arrives from your bot, Telegram clients clear its typing status). Returns True on success. + /// + /// > Example: The [ImageBot] needs some time to process a request and upload the image. Instead of sending a text message along the lines of “Retrieving image, please waitâ€Ļ”, the bot may use sendChatAction with action = upload_photo. The user will see a “sending photo” status for the bot. + /// + /// We only recommend using this method when a response from the bot will take a **noticeable** amount of time to arrive. + /// + /// [ImageBot]: https://t.me/imagebot + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SendChatAction (SendChatActionSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Type of action to broadcast. Choose one, depending on what the user is about to receive: typing for [text messages], upload_photo for [photos], record_video or upload_video for [videos], record_audio or upload_audio for [audio files], upload_document for [general files], find_location for [location data], record_video_note or upload_video_note for [video notes]. + /// + /// [audio files]: crate::payloads::SendAudio + /// [video notes]: crate::payloads::SendVideoNote + /// [videos]: crate::payloads::SendVideo + /// [location data]: crate::payloads::SendLocation + /// [text messages]: crate::payloads::SendMessage + /// [general files]: crate::payloads::SendDocument + /// [photos]: crate::payloads::SendPhoto + pub action: ChatAction, + } + } +} diff --git a/src/payloads/send_contact.rs b/src/payloads/send_contact.rs new file mode 100644 index 00000000..3d3d75de --- /dev/null +++ b/src/payloads/send_contact.rs @@ -0,0 +1,42 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, Message, ReplyMarkup}; + +impl_payload! { + /// Use this method to send phone contacts. On success, the sent [`Message`] is returned. + /// + /// [`Message`]: crate::types::Message + #[derive(Debug, PartialEq, Clone, Serialize)] + pub SendContact (SendContactSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Contact's phone number + pub phone_number: f64, + /// Contact's first name + pub first_name: f64, + } + optional { + /// Contact's last name + pub last_name: String [into], + /// Additional data about the contact in the form of a [vCard], 0-2048 bytes + /// + /// [vCard]: https://en.wikipedia.org/wiki/VCard + pub vcard: String [into], + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// If the message is a reply, ID of the original message + pub reply_to_message_id: i64, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub reply_markup: ReplyMarkup [into], + } + } +} diff --git a/src/payloads/send_dice.rs b/src/payloads/send_dice.rs new file mode 100644 index 00000000..6fb35e15 --- /dev/null +++ b/src/payloads/send_dice.rs @@ -0,0 +1,34 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, DiceEmoji, Message, ReplyMarkup}; + +impl_payload! { + /// Use this method to send an animated emoji that will display a random value. On success, the sent [`Message`] is returned. + /// + /// [`Message`]: crate::types::Message + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SendDice (SendDiceSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Emoji on which the dice throw animation is based. Currently, must be one of “🎲”, â€œđŸŽ¯â€, or “🏀”. Dice can have values 1-6 for “🎲” and â€œđŸŽ¯â€, and values 1-5 for “🏀”. Defaults to “🎲” + pub emoji: DiceEmoji, + } + optional { + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// If the message is a reply, ID of the original message + pub reply_to_message_id: i64, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: ReplyMarkup [into], + } + } +} diff --git a/src/payloads/send_document.rs b/src/payloads/send_document.rs new file mode 100644 index 00000000..206281c7 --- /dev/null +++ b/src/payloads/send_document.rs @@ -0,0 +1,46 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}; + +impl_payload! { + /// Use this method to send general files. On success, the sent [`Message`] is returned. Bots can currently send files of any type of up to 50 MB in size, this limit may be changed in the future. + /// + /// [`Message`]: crate::types::Message + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SendDocument (SendDocumentSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// File to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. [More info on Sending Files Âģ] + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + pub document: InputFile, + /// Document caption (may also be used when resending documents by _file\_id_), 0-1024 characters after entities parsing + pub caption: String [into], + } + optional { + /// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under . [More info on Sending Files Âģ] + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + pub thumb: InputFile, + /// Mode for parsing entities in the audio caption. See [formatting options] for more details. + /// + /// [formatting options]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: ParseMode, + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// If the message is a reply, ID of the original message + pub reply_to_message_id: i64, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub reply_markup: ReplyMarkup [into], + } + } +} diff --git a/src/payloads/send_invoice.rs b/src/payloads/send_invoice.rs new file mode 100644 index 00000000..1b9afc4d --- /dev/null +++ b/src/payloads/send_invoice.rs @@ -0,0 +1,71 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{InlineKeyboardMarkup, LabeledPrice, Message}; + +impl_payload! { + /// Use this method to send invoices. On success, the sent [`Message`] is returned. + /// + /// [`Message`]: crate::types::Message + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SendInvoice (SendInvoiceSetters) => Message { + required { + /// Unique identifier for the target private chat + pub chat_id: u32, + /// Product name, 1-32 characters + pub title: String [into], + /// Product description, 1-255 characters + pub description: String [into], + /// Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes. + pub payload: String [into], + /// Payments provider token, obtained via [Botfather] + /// + /// [Botfather]: https://t.me/botfather + pub provider_token: String [into], + /// Unique deep-linking parameter that can be used to generate this invoice when used as a start parameter + pub start_parameter: String [into], + /// Three-letter ISO 4217 currency code, see more on currencies + pub currency: String [into], + /// Price breakdown, a JSON-serialized list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.) + pub prices: Vec [collect], + } + optional { + /// A JSON-serialized data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider. + pub provider_data: String [into], + /// URL of the product photo for the invoice. Can be a photo of the goods or a marketing image for a service. People like it better when they see what they are paying for. + pub photo_url: String [into], + /// Photo size + pub photo_size: String [into], + /// Photo width + pub photo_width: String [into], + /// Photo height + pub photo_height: String [into], + /// Pass _True_, if you require the user's full name to complete the order + pub need_name: bool, + /// Pass _True_, if you require the user's phone number to complete the order + pub need_phone_number: bool, + /// Pass _True_, if you require the user's email address to complete the order + pub need_email: bool, + /// Pass _True_, if you require the user's shipping address to complete the order + pub need_shipping_address: bool, + /// Pass _True_, if user's phone number should be sent to provider + pub send_phone_number_to_provider: bool, + /// Pass _True_, if user's email address should be sent to provider + pub send_email_to_provider: bool, + /// Pass _True_, if the final price depends on the shipping method + pub is_flexible: bool, + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// If the message is a reply, ID of the original message + pub reply_to_message_id: i64, + /// A JSON-serialized object for an [inline keyboard]. If empty, one 'Pay `total price`' button will be shown. If not empty, the first button must be a Pay button. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: InlineKeyboardMarkup, + } + } +} diff --git a/src/payloads/send_location.rs b/src/payloads/send_location.rs new file mode 100644 index 00000000..45a988ce --- /dev/null +++ b/src/payloads/send_location.rs @@ -0,0 +1,40 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, Message, ReplyMarkup}; + +impl_payload! { + /// Use this method to send point on the map. On success, the sent [`Message`] is returned. + /// + /// [`Message`]: crate::types::Message + #[derive(Debug, PartialEq, Clone, Serialize)] + pub SendLocation (SendLocationSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Latitude of the location + pub latitude: f64, + /// Longitude of the location + pub longitude: f64, + /// Period in seconds for which the location will be updated (see [Live Locations], should be between 60 and 86400. + /// + /// [Live Locations]: https://telegram.org/blog/live-locations + pub live_period: u32, + } + optional { + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// If the message is a reply, ID of the original message + pub reply_to_message_id: i64, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: ReplyMarkup [into], + } + } +} diff --git a/src/payloads/send_media_group.rs b/src/payloads/send_media_group.rs new file mode 100644 index 00000000..30a01a1e --- /dev/null +++ b/src/payloads/send_media_group.rs @@ -0,0 +1,29 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, InputMedia, Message}; + +impl_payload! { + /// Use this method to send a group of photos or videos as an album. On success, an array of the sent [`Message`]s is returned. + /// + /// [`Message`]: crate::types::Message + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SendMediaGroup (SendMediaGroupSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// A JSON-serialized array describing photos and videos to be sent, must include 2-10 items + pub media: Vec [collect], + } + optional { + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// If the message is a reply, ID of the original message + pub reply_to_message_id: i64, + } + } +} diff --git a/src/payloads/send_message.rs b/src/payloads/send_message.rs index 5262fb0d..ece90beb 100644 --- a/src/payloads/send_message.rs +++ b/src/payloads/send_message.rs @@ -1,42 +1,40 @@ -use serde::{Deserialize, Serialize}; +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; use crate::types::{ChatId, Message, ParseMode, ReplyMarkup}; impl_payload! { - /// Use this method to send text messages. - /// - /// On success, the sent [`Message`] is returned. + /// Use this method to send text messages. On success, the sent [`Message`] is returned. /// /// [`Message`]: crate::types::Message - #[serde_with_macros::skip_serializing_none] - #[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)] + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] pub SendMessage (SendMessageSetters) => Message { required { - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`) + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) pub chat_id: ChatId [into], - /// Text of the message to be sent + /// Text of the message to be sent, 1-4096 characters after entities parsing pub text: String [into], } optional { - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in your bot's message. + /// Mode for parsing entities in the message text. See [formatting options] for more details. /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: crate::types::ParseMode + /// [formatting options]: https://core.telegram.org/bots/api#formatting-options pub parse_mode: ParseMode, /// Disables link previews for links in this message pub disable_web_page_preview: bool, - /// Sends the message silently. - /// Users will receive a notification with no sound. - pub disable_notification: bool, - /// If the message is a reply, [id] of the original message + /// Sends the message [silently]. Users will receive a notification with no sound. /// - /// [id]: crate::types::Message::id - pub reply_to_message_id: i32, - /// Additional interface options. - pub reply_markup: ReplyMarkup, + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// If the message is a reply, ID of the original message + pub reply_to_message_id: i64, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: ReplyMarkup [into], } } } diff --git a/src/payloads/send_photo.rs b/src/payloads/send_photo.rs new file mode 100644 index 00000000..dc6ad339 --- /dev/null +++ b/src/payloads/send_photo.rs @@ -0,0 +1,42 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}; + +impl_payload! { + /// Use this method to send photos. On success, the sent [`Message`] is returned. + /// + /// [`Message`]: crate::types::Message + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SendPhoto (SendPhotoSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Photo to send. Pass a file_id as String to send a photo that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a photo from the Internet, or upload a new photo using multipart/form-data. [More info on Sending Files Âģ] + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + pub photo: InputFile, + /// Photo caption (may also be used when resending photos by _file\_id_), 0-1024 characters after entities parsing + pub caption: String [into], + } + optional { + /// Mode for parsing entities in the photo caption. See [formatting options] for more details. + /// + /// [formatting options]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: ParseMode, + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// If the message is a reply, ID of the original message + pub reply_to_message_id: i64, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub reply_markup: ReplyMarkup [into], + } + } +} diff --git a/src/payloads/send_poll.rs b/src/payloads/send_poll.rs new file mode 100644 index 00000000..8011d77b --- /dev/null +++ b/src/payloads/send_poll.rs @@ -0,0 +1,56 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, Message, ParseMode, PollType, ReplyMarkup}; + +impl_payload! { + /// Use this method to send phone contacts. On success, the sent [`Message`] is returned. + /// + /// [`Message`]: crate::types::Message + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SendPoll (SendPollSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Poll question, 1-255 characters + pub question: String [into], + /// A JSON-serialized list of answer options, 2-10 strings 1-100 characters each + pub options: Vec [collect], + /// Poll type, “quiz” or “regular”, defaults to “regular” + pub type_: PollType, + } + optional { + /// True, if the poll needs to be anonymous, defaults to True + pub is_anonymous: bool, + /// True, if the poll allows multiple answers, ignored for polls in quiz mode, defaults to False + pub allows_multiple_answers: bool, + /// 0-based identifier of the correct answer option, required for polls in quiz mode + pub correct_option_id: u8, + /// Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll, 0-200 characters with at most 2 line feeds after entities parsing + pub explanation: String [into], + /// Mode for parsing entities in the message text. See [formatting options] for more details. + /// + /// [formatting options]: https://core.telegram.org/bots/api#formatting-options + pub explanation_parse_mode: ParseMode, + /// Amount of time in seconds the poll will be active after creation, 5-600. Can't be used together with close_date. + pub open_period: u16, + /// Point in time (Unix timestamp) when the poll will be automatically closed. Must be at least 5 and no more than 600 seconds in the future. Can't be used together with open_period. + pub close_date: u64, + /// Pass True, if the poll needs to be immediately closed. This can be useful for poll preview. + pub is_closed: bool, + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// If the message is a reply, ID of the original message + pub reply_to_message_id: i64, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub reply_markup: ReplyMarkup [into], + } + } +} diff --git a/src/payloads/send_sticker.rs b/src/payloads/send_sticker.rs new file mode 100644 index 00000000..8a797b6e --- /dev/null +++ b/src/payloads/send_sticker.rs @@ -0,0 +1,36 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, InputFile, Message, ReplyMarkup}; + +impl_payload! { + /// Use this method to send static .WEBP or [animated] .TGS stickers. On success, the sent Message is returned. + /// + /// [animated]: https://telegram.org/blog/animated-stickers + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SendSticker (SendStickerSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`). + pub chat_id: ChatId [into], + /// Sticker to send. Pass a file_id as String to send a photo that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a photo from the Internet, or upload a new photo using multipart/form-data. [More info on Sending Files Âģ] + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + pub sticker: InputFile, + } + optional { + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// If the message is a reply, ID of the original message + pub reply_to_message_id: i64, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub reply_markup: ReplyMarkup [into], + } + } +} diff --git a/src/payloads/send_venue.rs b/src/payloads/send_venue.rs new file mode 100644 index 00000000..f8394ec3 --- /dev/null +++ b/src/payloads/send_venue.rs @@ -0,0 +1,44 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, Message, ReplyMarkup}; + +impl_payload! { + /// Use this method to send information about a venue. On success, the sent [`Message`] is returned. + /// + /// [`Message`]: crate::types::Message + #[derive(Debug, PartialEq, Clone, Serialize)] + pub SendVenue (SendVenueSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Latitude of new location + pub latitude: f64, + /// Longitude of new location + pub longitude: f64, + /// Name of the venue + pub title: String [into], + /// Address of the venue + pub address: String [into], + } + optional { + /// Foursquare identifier of the venue + pub foursquare_id: String [into], + /// Foursquare type of the venue, if known. (For example, “arts_entertainment/default”, “arts_entertainment/aquarium” or “food/icecream”.) + pub foursquare_type: String [into], + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// If the message is a reply, ID of the original message + pub reply_to_message_id: i64, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: ReplyMarkup [into], + } + } +} diff --git a/src/payloads/send_video.rs b/src/payloads/send_video.rs new file mode 100644 index 00000000..a23bb980 --- /dev/null +++ b/src/payloads/send_video.rs @@ -0,0 +1,55 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}; + +impl_payload! { + /// Use this method to send video files, Telegram clients support mp4 videos (other formats may be sent as [`Document`]). On success, the sent [`Message`] is returned. Bots can currently send video files of up to 50 MB in size, this limit may be changed in the future. + /// + /// [`Message`]: crate::types::Message + /// [`Document`]: crate::types::Document + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SendVideo (SendVideoSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Video to send. Pass a file_id as String to send a video that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a video from the Internet, or upload a new video using multipart/form-data. [More info on Sending Files Âģ] + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + pub video: InputFile, + /// Video caption (may also be used when resending videos by _file\_id_), 0-1024 characters after entities parsing + pub caption: String [into], + } + optional { + /// Duration of the video in seconds + pub duration: u32, + /// Video width + pub width: u32, + /// Video height + pub height: u32, + /// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under . [More info on Sending Files Âģ] + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + pub thumb: InputFile, + /// Mode for parsing entities in the video caption. See [formatting options] for more details. + /// + /// [formatting options]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: ParseMode, + /// Pass _True_, if the uploaded video is suitable for streaming + pub supports_streaming: bool, + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// If the message is a reply, ID of the original message + pub reply_to_message_id: i64, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub reply_markup: ReplyMarkup [into], + } + } +} diff --git a/src/payloads/send_video_note.rs b/src/payloads/send_video_note.rs new file mode 100644 index 00000000..282c990a --- /dev/null +++ b/src/payloads/send_video_note.rs @@ -0,0 +1,45 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, InputFile, Message, ReplyMarkup}; + +impl_payload! { + /// 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. On success, the sent [`Message`] is returned. + /// + /// [`Message`]: crate::types::Message + /// [v.4.0]: https://core.telegram.org/bots/api#document + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SendVideoNote (SendVideoNoteSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Video note to send. Pass a file_id as String to send a video note that exists on the Telegram servers (recommended) or upload a new video using multipart/form-data. [More info on Sending Files Âģ]. Sending video notes by a URL is currently unsupported + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + pub video_note: InputFile, + } + optional { + /// Duration of the video in seconds + pub duration: u32, + /// Video width and height, i.e. diameter of the video message + pub length: u32, + /// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under . [More info on Sending Files Âģ] + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + pub thumb: InputFile, + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// If the message is a reply, ID of the original message + pub reply_to_message_id: i64, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub reply_markup: ReplyMarkup [into], + } + } +} diff --git a/src/payloads/send_voice.rs b/src/payloads/send_voice.rs new file mode 100644 index 00000000..0a27dfa4 --- /dev/null +++ b/src/payloads/send_voice.rs @@ -0,0 +1,46 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}; + +impl_payload! { + /// Use this method to send audio files, if you want Telegram clients to display the file as a playable voice message. For this to work, your audio must be in an .OGG file encoded with OPUS (other formats may be sent as [`Audio`] or [`Document`]). On success, the sent [`Message`] is returned. Bots can currently send voice messages of up to 50 MB in size, this limit may be changed in the future. + /// + /// [`Message`]: crate::types::Message + /// [`Document`]: crate::types::Document + /// [`Audio`]: crate::types::Audio + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SendVoice (SendVoiceSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Audio file to send. Pass a file_id as String to send an audio file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an audio file from the Internet, or upload a new one using multipart/form-data. [More info on Sending Files Âģ] + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + pub voice: InputFile, + /// Voice message caption, 0-1024 characters after entities parsing + pub caption: String [into], + } + optional { + /// Mode for parsing entities in the voice message caption. See [formatting options] for more details. + /// + /// [formatting options]: https://core.telegram.org/bots/api#formatting-options + pub parse_mode: ParseMode, + /// Duration of the voice message in seconds + pub duration: u32, + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// If the message is a reply, ID of the original message + pub reply_to_message_id: i64, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub reply_markup: ReplyMarkup [into], + } + } +} diff --git a/src/payloads/set_chat_administrator_custom_title.rs b/src/payloads/set_chat_administrator_custom_title.rs new file mode 100644 index 00000000..144f0f9d --- /dev/null +++ b/src/payloads/set_chat_administrator_custom_title.rs @@ -0,0 +1,21 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, True}; + +impl_payload! { + /// Use this method to set a custom title for an administrator in a supergroup promoted by the bot. Returns _True_on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SetChatAdministratorCustomTitle (SetChatAdministratorCustomTitleSetters) => True { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Unique identifier of the target user + pub user_id: u32, + /// New custom title for the administrator; 0-16 characters, emoji are not allowed + pub custom_title: String [into], + } + } +} diff --git a/src/payloads/set_chat_description.rs b/src/payloads/set_chat_description.rs new file mode 100644 index 00000000..f42c7908 --- /dev/null +++ b/src/payloads/set_chat_description.rs @@ -0,0 +1,21 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::ChatId; + +impl_payload! { + /// Use this method to change the description of a group, a supergroup or a channel. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Returns _True_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SetChatDescription (SetChatDescriptionSetters) => String { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + } + optional { + /// New chat description, 0-255 characters + pub description: String [into], + } + } +} diff --git a/src/payloads/set_chat_permissions.rs b/src/payloads/set_chat_permissions.rs new file mode 100644 index 00000000..1b2e0ea4 --- /dev/null +++ b/src/payloads/set_chat_permissions.rs @@ -0,0 +1,19 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, ChatPermissions, True}; + +impl_payload! { + /// Use this method to set default chat permissions for all members. The bot must be an administrator in the group or a supergroup for this to work and must have the _can_restrict_members_ admin rights. Returns _True_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SetChatPermissions (SetChatPermissionsSetters) => True { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// New default chat permissions + pub permissions: ChatPermissions, + } + } +} diff --git a/src/payloads/set_chat_photo.rs b/src/payloads/set_chat_photo.rs new file mode 100644 index 00000000..cbeeba58 --- /dev/null +++ b/src/payloads/set_chat_photo.rs @@ -0,0 +1,19 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, InputFile}; + +impl_payload! { + /// Use this method to set a new profile photo for the chat. Photos can't be changed for private chats. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Returns _True_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SetChatPhoto (SetChatPhotoSetters) => String { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// New chat photo, uploaded using multipart/form-data + pub photo: InputFile, + } + } +} diff --git a/src/payloads/set_chat_sticker_set.rs b/src/payloads/set_chat_sticker_set.rs new file mode 100644 index 00000000..42e0e8e8 --- /dev/null +++ b/src/payloads/set_chat_sticker_set.rs @@ -0,0 +1,19 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, True}; + +impl_payload! { + /// Use this method to set a new group sticker set for a supergroup. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Use the field _can\_set\_sticker\_set_ optionally returned in getChat requests to check if the bot can use this method. Returns _True_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SetChatStickerSet (SetChatStickerSetSetters) => True { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Name of the sticker set to be set as the group sticker set + pub sticker_set_name: String [into], + } + } +} diff --git a/src/payloads/set_chat_title.rs b/src/payloads/set_chat_title.rs new file mode 100644 index 00000000..9c1982c7 --- /dev/null +++ b/src/payloads/set_chat_title.rs @@ -0,0 +1,19 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::ChatId; + +impl_payload! { + /// Use this method to change the title of a chat. Titles can't be changed for private chats. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Returns _True_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SetChatTitle (SetChatTitleSetters) => String { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// New chat title, 1-255 characters + pub title: String [into], + } + } +} diff --git a/src/payloads/set_my_commands.rs b/src/payloads/set_my_commands.rs new file mode 100644 index 00000000..4224b9f1 --- /dev/null +++ b/src/payloads/set_my_commands.rs @@ -0,0 +1,17 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::BotCommand; + +impl_payload! { + /// Use this method to change the list of the bot's commands. Returns _True_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SetMyCommands (SetMyCommandsSetters) => u32 { + required { + /// A JSON-serialized list of bot commands to be set as the list of the bot's commands. At most 100 commands can be specified. + pub commands: Vec [collect], + } + } +} diff --git a/src/payloads/set_passport_data_errors.rs b/src/payloads/set_passport_data_errors.rs new file mode 100644 index 00000000..686b63b4 --- /dev/null +++ b/src/payloads/set_passport_data_errors.rs @@ -0,0 +1,21 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{PassportElementError, True}; + +impl_payload! { + /// Informs a user that some of the Telegram Passport elements they provided contains errors. The user will not be able to re-submit their Passport to you until the errors are fixed (the contents of the field for which you returned the error must change). Returns _True_ on success. + /// + /// Use this if the data submitted by the user doesn't satisfy the standards your service requires for any reason. For example, if a birthday date seems invalid, a submitted document is blurry, a scan shows evidence of tampering, etc. Supply some details in the error message to make sure the user knows how to correct the issues. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SetPassportDataErrors (SetPassportDataErrorsSetters) => True { + required { + /// User identifier + pub user_id: u32, + /// A JSON-serialized array describing the errors + pub errors: Vec [collect], + } + } +} diff --git a/src/payloads/set_sticker_position_in_set.rs b/src/payloads/set_sticker_position_in_set.rs new file mode 100644 index 00000000..05ef8b1c --- /dev/null +++ b/src/payloads/set_sticker_position_in_set.rs @@ -0,0 +1,19 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::True; + +impl_payload! { + /// Use this method to move a sticker in a set created by the bot to a specific position. Returns _True_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SetStickerPositionInSet (SetStickerPositionInSetSetters) => True { + required { + /// File identifier of the sticker + pub sticker: String [into], + /// New sticker position in the set, zero-based + pub position: u32, + } + } +} diff --git a/src/payloads/set_sticker_set_thumb.rs b/src/payloads/set_sticker_set_thumb.rs new file mode 100644 index 00000000..a9cef002 --- /dev/null +++ b/src/payloads/set_sticker_set_thumb.rs @@ -0,0 +1,25 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{InputFile, True}; + +impl_payload! { + /// Use this method to set the thumbnail of a sticker set. Animated thumbnails can be set for animated sticker sets only. Returns _True_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SetStickerSetThumb (SetStickerSetThumbSetters) => True { + required { + /// Name of the sticker set + pub name: String [into], + /// User identifier of sticker file owner + pub user_id: u32, + } + optional { + /// A **PNG** image with the thumbnail, must be up to 128 kilobytes in size and have width and height exactly 100px, or a **TGS** animation with the thumbnail up to 32 kilobytes in size; see https://core.telegram.org/animated_stickers#technical-requirements for animated sticker technical requirements. Pass a _file\_id_ as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. [More info on Sending Files Âģ]. Animated sticker set thumbnail can't be uploaded via HTTP URL. + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + pub thumb: InputFile, + } + } +} diff --git a/src/payloads/set_webhook.rs b/src/payloads/set_webhook.rs new file mode 100644 index 00000000..c3c8c53f --- /dev/null +++ b/src/payloads/set_webhook.rs @@ -0,0 +1,35 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{AllowedUpdate, InputFile, True}; + +impl_payload! { + /// Use this method to specify a url and receive incoming updates via an outgoing webhook. Whenever there is an update for the bot, we will send an HTTPS POST request to the specified url, containing a JSON-serialized [`Update`]. In case of an unsuccessful request, we will give up after a reasonable amount of attempts. Returns True on success. + /// + /// If you'd like to make sure that the Webhook request comes from Telegram, we recommend using a secret path in the URL, e.g. `https://www.example.com/`. Since nobody else knows your bot's token, you can be pretty sure it's us. + /// + /// [`Update`]: crate::types::Update + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SetWebhook (SetWebhookSetters) => True { + required { + /// HTTPS url to send updates to. Use an empty string to remove webhook integration + pub url: String [into], + /// A JSON-serialized list of the update types you want your bot to receive. For example, specify [“message”, “edited_channel_post”, “callback_query”] to only receive updates of these types. See [`Update`] for a complete list of available update types. Specify an empty list to receive all updates regardless of type (default). If not specified, the previous setting will be used. + /// + /// Please note that this parameter doesn't affect updates created before the call to the setWebhook, so unwanted updates may be received for a short period of time. + /// + /// [`Update`]: crate::types::Update + pub allowed_updates: Vec [collect], + } + optional { + /// Upload your public key certificate so that the root certificate in use can be checked. See our [self-signed guide] for details. + /// + /// [self-signed guide]: https://core.telegram.org/bots/self-signed + pub certificate: InputFile, + /// Maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to 40. Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput. + pub max_connections: u8, + } + } +} diff --git a/src/payloads/setters.rs b/src/payloads/setters.rs index 110698ae..98b55c80 100644 --- a/src/payloads/setters.rs +++ b/src/payloads/setters.rs @@ -1 +1,34 @@ -pub use crate::payloads::{GetMeSetters as _, SendMessageSetters as _}; +// This file is auto generated by `cg` (878e847). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. + +#[doc(no_inline)] +pub use crate::payloads::{ + AddStickerToSetSetters as _, AnswerCallbackQuerySetters as _, AnswerInlineQuerySetters as _, + AnswerPreCheckoutQuerySetters as _, AnswerShippingQuerySetters as _, + CreateNewStickerSetSetters as _, DeleteChatPhotoSetters as _, DeleteChatStickerSetSetters as _, + DeleteMessageSetters as _, DeleteStickerFromSetSetters as _, DeleteWebhookSetters as _, + EditMessageCaptionInlineSetters as _, EditMessageCaptionSetters as _, + EditMessageLiveLocationInlineSetters as _, EditMessageLiveLocationSetters as _, + EditMessageMediaInlineSetters as _, EditMessageMediaSetters as _, + EditMessageReplyMarkupInlineSetters as _, EditMessageReplyMarkupSetters as _, + EditMessageTextInlineSetters as _, EditMessageTextSetters as _, + ExportChatInviteLinkSetters as _, ForwardMessageSetters as _, + GetChatAdministratorsSetters as _, GetChatMemberSetters as _, GetChatMembersCountSetters as _, + GetChatSetters as _, GetFileSetters as _, GetMeSetters as _, GetMyCommandsSetters as _, + GetStickerSetSetters as _, GetUpdatesSetters as _, GetUserProfilePhotosSetters as _, + GetWebhookInfoSetters as _, KickChatMemberSetters as _, LeaveChatSetters as _, + PinChatMessageSetters as _, PromoteChatMemberSetters as _, RestrictChatMemberSetters as _, + SendAnimationSetters as _, SendAudioSetters as _, SendChatActionSetters as _, + SendContactSetters as _, SendDiceSetters as _, SendDocumentSetters as _, + SendInvoiceSetters as _, SendLocationSetters as _, SendMediaGroupSetters as _, + SendMessageSetters as _, SendPhotoSetters as _, SendPollSetters as _, SendStickerSetters as _, + SendVenueSetters as _, SendVideoNoteSetters as _, SendVideoSetters as _, SendVoiceSetters as _, + SetChatAdministratorCustomTitleSetters as _, SetChatDescriptionSetters as _, + SetChatPermissionsSetters as _, SetChatPhotoSetters as _, SetChatStickerSetSetters as _, + SetChatTitleSetters as _, SetMyCommandsSetters as _, SetPassportDataErrorsSetters as _, + SetStickerPositionInSetSetters as _, SetStickerSetThumbSetters as _, SetWebhookSetters as _, + StopMessageLiveLocationInlineSetters as _, StopMessageLiveLocationSetters as _, + StopPollSetters as _, UnbanChatMemberSetters as _, UnpinChatMessageSetters as _, + UploadStickerFileSetters as _, +}; diff --git a/src/payloads/stop_message_live_location.rs b/src/payloads/stop_message_live_location.rs new file mode 100644 index 00000000..bec8af47 --- /dev/null +++ b/src/payloads/stop_message_live_location.rs @@ -0,0 +1,35 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, Message, ReplyMarkup}; + +impl_payload! { + /// Use this method to edit live location messages. A location can be edited until its live_period expires or editing is explicitly disabled by a call to [`StopMessageLiveLocation`]. On success, the edited Message is returned. + /// + /// See also: [`StopMessageLiveLocationInline`](crate::payloads::StopMessageLiveLocationInline) + /// + /// [`Message`]: crate::types::Message + /// [`StopMessageLiveLocation`]: crate::payloads::StopMessageLiveLocation + #[derive(Debug, PartialEq, Clone, Serialize)] + pub StopMessageLiveLocation (StopMessageLiveLocationSetters) => Message { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Identifier of the message to edit + pub message_id: i64, + /// Latitude of new location + pub latitude: f64, + /// Longitude of new location + pub longitude: f64, + } + optional { + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + pub reply_markup: ReplyMarkup [into], + } + } +} diff --git a/src/payloads/stop_message_live_location_inline.rs b/src/payloads/stop_message_live_location_inline.rs new file mode 100644 index 00000000..b9b7c40c --- /dev/null +++ b/src/payloads/stop_message_live_location_inline.rs @@ -0,0 +1,32 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{Message, ReplyMarkup}; + +impl_payload! { + /// Use this method to edit live location messages. A location can be edited until its live_period expires or editing is explicitly disabled by a call to [`StopMessageLiveLocation`]. On success, True is returned. + /// + /// See also: [`StopMessageLiveLocation`](crate::payloads::StopMessageLiveLocation) + /// + /// [`StopMessageLiveLocation`]: crate::payloads::StopMessageLiveLocation + #[derive(Debug, PartialEq, Clone, Serialize)] + pub StopMessageLiveLocationInline (StopMessageLiveLocationInlineSetters) => Message { + required { + /// Identifier of the inline message + pub inline_message_id: String [into], + /// Latitude of new location + pub latitude: f64, + /// Longitude of new location + pub longitude: f64, + } + optional { + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: ReplyMarkup [into], + } + } +} diff --git a/src/payloads/stop_poll.rs b/src/payloads/stop_poll.rs new file mode 100644 index 00000000..fc624c05 --- /dev/null +++ b/src/payloads/stop_poll.rs @@ -0,0 +1,25 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, InlineKeyboardMarkup, Poll}; + +impl_payload! { + /// Use this method to stop a poll which was sent by the bot. On success, the stopped Poll with the final results is returned. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub StopPoll (StopPollSetters) => Poll { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`). + pub chat_id: ChatId [into], + /// Identifier of the message to edit + pub message_id: i64, + } + optional { + /// A JSON-serialized object for an [inline keyboard]. + /// + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + pub reply_markup: InlineKeyboardMarkup, + } + } +} diff --git a/src/payloads/unban_chat_member.rs b/src/payloads/unban_chat_member.rs new file mode 100644 index 00000000..83ba27d5 --- /dev/null +++ b/src/payloads/unban_chat_member.rs @@ -0,0 +1,19 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{ChatId, True}; + +impl_payload! { + /// 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, but will be able to join via link, etc. The bot must be an administrator for this to work. Returns _True_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub UnbanChatMember (UnbanChatMemberSetters) => True { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + /// Unique identifier of the target user + pub user_id: u32, + } + } +} diff --git a/src/payloads/unpin_chat_message.rs b/src/payloads/unpin_chat_message.rs new file mode 100644 index 00000000..dc6e35fe --- /dev/null +++ b/src/payloads/unpin_chat_message.rs @@ -0,0 +1,17 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::ChatId; + +impl_payload! { + /// Use this method to unpin a message in a group, a supergroup, or a channel. The bot must be an administrator in the chat for this to work and must have the 'can_pin_messages' admin right in the supergroup or 'can_edit_messages' admin right in the channel. Returns _True_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub UnpinChatMessage (UnpinChatMessageSetters) => String { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: ChatId [into], + } + } +} diff --git a/src/payloads/upload_sticker_file.rs b/src/payloads/upload_sticker_file.rs new file mode 100644 index 00000000..46e8976e --- /dev/null +++ b/src/payloads/upload_sticker_file.rs @@ -0,0 +1,21 @@ +// This file is auto generated by `cg` (bc65e6f). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{File, InputFile}; + +impl_payload! { + /// Use this method to upload a .PNG file with a sticker for later use in _createNewStickerSet_ and _addStickerToSet_ methods (can be used multiple times). Returns the uploaded File on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub UploadStickerFile (UploadStickerFileSetters) => File { + required { + /// User identifier of sticker file owner + pub user_id: u32, + /// PNG image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. [More info on Sending Files Âģ] + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + pub png_sticker: InputFile, + } + } +} From f0609c06bc8c06fbb8b9a232014f5b4846f75422 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 13 Nov 2020 19:15:09 +0300 Subject: [PATCH 145/755] Add docs for setters mod --- src/payloads/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/payloads/mod.rs b/src/payloads/mod.rs index 2316d16e..3867431b 100644 --- a/src/payloads/mod.rs +++ b/src/payloads/mod.rs @@ -1,5 +1,10 @@ //! Payloads - data types sended to relegram +/// This module reexports all setters traits as `_`. When used with a glob import: +/// ``` +/// use teloxide_core::payloads::setters::*; +/// ``` +/// It allows you to use all payloads setters, without polluting your namespace. pub mod setters; // This block is auto generated by `cg` (878e847). From 62b65cb7a64df30767de45720f21f223fc907d2b Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 13 Nov 2020 20:47:47 +0300 Subject: [PATCH 146/755] clippy --- src/local_macros.rs | 4 ++++ src/payloads/add_sticker_to_set.rs | 2 +- src/payloads/answer_callback_query.rs | 6 +++--- src/payloads/answer_inline_query.rs | 2 +- src/payloads/answer_pre_checkout_query.rs | 2 +- src/payloads/answer_shipping_query.rs | 2 +- src/payloads/create_new_sticker_set.rs | 2 +- src/payloads/delete_chat_photo.rs | 2 +- src/payloads/delete_chat_sticker_set.rs | 2 +- src/payloads/delete_message.rs | 2 +- src/payloads/delete_sticker_from_set.rs | 2 +- src/payloads/delete_webhook.rs | 4 ++-- src/payloads/edit_message_caption.rs | 2 +- src/payloads/edit_message_caption_inline.rs | 2 +- src/payloads/edit_message_live_location.rs | 4 ++-- src/payloads/edit_message_live_location_inline.rs | 2 +- src/payloads/edit_message_media.rs | 2 +- src/payloads/edit_message_media_inline.rs | 2 +- src/payloads/edit_message_reply_markup.rs | 2 +- src/payloads/edit_message_reply_markup_inline.rs | 2 +- src/payloads/edit_message_text.rs | 2 +- src/payloads/edit_message_text_inline.rs | 2 +- src/payloads/export_chat_invite_link.rs | 2 +- src/payloads/forward_message.rs | 2 +- src/payloads/get_chat.rs | 2 +- src/payloads/get_chat_administrators.rs | 2 +- src/payloads/get_chat_member.rs | 2 +- src/payloads/get_chat_members_count.rs | 2 +- src/payloads/get_file.rs | 2 +- src/payloads/get_me.rs | 4 ++-- src/payloads/get_my_commands.rs | 4 ++-- src/payloads/get_sticker_set.rs | 2 +- src/payloads/get_updates.rs | 6 +++--- src/payloads/get_user_profile_photos.rs | 2 +- src/payloads/get_webhook_info.rs | 4 ++-- src/payloads/kick_chat_member.rs | 2 +- src/payloads/leave_chat.rs | 2 +- src/payloads/mod.rs | 3 ++- src/payloads/pin_chat_message.rs | 2 +- src/payloads/promote_chat_member.rs | 2 +- src/payloads/restrict_chat_member.rs | 2 +- src/payloads/send_animation.rs | 4 ++-- src/payloads/send_audio.rs | 6 +++--- src/payloads/send_chat_action.rs | 10 +++++----- src/payloads/send_contact.rs | 2 +- src/payloads/send_dice.rs | 4 ++-- src/payloads/send_document.rs | 2 +- src/payloads/send_invoice.rs | 2 +- src/payloads/send_location.rs | 4 ++-- src/payloads/send_media_group.rs | 2 +- src/payloads/send_message.rs | 4 ++-- src/payloads/send_photo.rs | 2 +- src/payloads/send_poll.rs | 2 +- src/payloads/send_sticker.rs | 4 ++-- src/payloads/send_venue.rs | 4 ++-- src/payloads/send_video.rs | 4 ++-- src/payloads/send_video_note.rs | 4 ++-- src/payloads/send_voice.rs | 4 ++-- src/payloads/set_chat_administrator_custom_title.rs | 2 +- src/payloads/set_chat_description.rs | 2 +- src/payloads/set_chat_permissions.rs | 2 +- src/payloads/set_chat_photo.rs | 2 +- src/payloads/set_chat_sticker_set.rs | 2 +- src/payloads/set_chat_title.rs | 2 +- src/payloads/set_my_commands.rs | 2 +- src/payloads/set_passport_data_errors.rs | 2 +- src/payloads/set_sticker_position_in_set.rs | 2 +- src/payloads/set_sticker_set_thumb.rs | 2 +- src/payloads/set_webhook.rs | 2 +- src/payloads/stop_message_live_location.rs | 4 ++-- src/payloads/stop_message_live_location_inline.rs | 2 +- src/payloads/stop_poll.rs | 2 +- src/payloads/unban_chat_member.rs | 2 +- src/payloads/unpin_chat_message.rs | 2 +- src/payloads/upload_sticker_file.rs | 2 +- 75 files changed, 104 insertions(+), 99 deletions(-) diff --git a/src/local_macros.rs b/src/local_macros.rs index 1a60dcbf..9f6947b3 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -184,6 +184,10 @@ macro_rules! impl_payload { } impl $Method { + // We mirror telegram API and can't do anything with too many arguments + #[allow(clippy::too_many_arguments)] + // It's just easier for macros to generate such code + #[allow(clippy::redundant_field_names)] $vi fn new($($($fields : impl_payload!(@convert? $FTy $([$conv])?)),*)?) -> Self { Self { $( diff --git a/src/payloads/add_sticker_to_set.rs b/src/payloads/add_sticker_to_set.rs index 518c780e..12a287cb 100644 --- a/src/payloads/add_sticker_to_set.rs +++ b/src/payloads/add_sticker_to_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/answer_callback_query.rs b/src/payloads/answer_callback_query.rs index 10a9e487..cc269c57 100644 --- a/src/payloads/answer_callback_query.rs +++ b/src/payloads/answer_callback_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -10,8 +10,8 @@ impl_payload! { /// /// >Alternatively, the user can be redirected to the specified Game URL. For this option to work, you must first create a game for your bot via [@Botfather] and accept the terms. Otherwise, you may use links like `t.me/your_bot?start=XXXX` that open your bot with a parameter. /// - /// [inline keyboards]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [@Botfather]: https://t.me/botfather + /// [inline keyboards]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] pub AnswerCallbackQuery (AnswerCallbackQuerySetters) => True { required { @@ -28,8 +28,8 @@ impl_payload! { /// Otherwise, you may use links like `t.me/your\_bot?start=XXXX` that open your bot with a parameter. /// /// [@Botfather]: https://t.me/botfather - /// [`Game`]: crate::types::Game /// [callback_game]: https://core.telegram.org/bots/api#inlinekeyboardbutton + /// [`Game`]: crate::types::Game pub url: String [into], /// The maximum amount of time in seconds that the result of the callback query may be cached client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0. pub cache_time: u32, diff --git a/src/payloads/answer_inline_query.rs b/src/payloads/answer_inline_query.rs index f892959b..3c41547b 100644 --- a/src/payloads/answer_inline_query.rs +++ b/src/payloads/answer_inline_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/answer_pre_checkout_query.rs b/src/payloads/answer_pre_checkout_query.rs index 8cfbd7d5..e5a2b4ff 100644 --- a/src/payloads/answer_pre_checkout_query.rs +++ b/src/payloads/answer_pre_checkout_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/answer_shipping_query.rs b/src/payloads/answer_shipping_query.rs index 471b35b9..b04c3995 100644 --- a/src/payloads/answer_shipping_query.rs +++ b/src/payloads/answer_shipping_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/create_new_sticker_set.rs b/src/payloads/create_new_sticker_set.rs index cbeb51a8..07d822c5 100644 --- a/src/payloads/create_new_sticker_set.rs +++ b/src/payloads/create_new_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_chat_photo.rs b/src/payloads/delete_chat_photo.rs index 2bd8a6cc..4a9672fd 100644 --- a/src/payloads/delete_chat_photo.rs +++ b/src/payloads/delete_chat_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_chat_sticker_set.rs b/src/payloads/delete_chat_sticker_set.rs index e5ca6230..0acad4c9 100644 --- a/src/payloads/delete_chat_sticker_set.rs +++ b/src/payloads/delete_chat_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_message.rs b/src/payloads/delete_message.rs index 039bcb38..a1eecabd 100644 --- a/src/payloads/delete_message.rs +++ b/src/payloads/delete_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_sticker_from_set.rs b/src/payloads/delete_sticker_from_set.rs index 2b6396aa..faa1b962 100644 --- a/src/payloads/delete_sticker_from_set.rs +++ b/src/payloads/delete_sticker_from_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_webhook.rs b/src/payloads/delete_webhook.rs index ae4907c4..22011bbf 100644 --- a/src/payloads/delete_webhook.rs +++ b/src/payloads/delete_webhook.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -9,7 +9,7 @@ impl_payload! { /// Use this method to remove webhook integration if you decide to switch back to [`GetUpdates`]. Returns True on success. Requires no parameters. /// /// [`GetUpdates`]: crate::payloads::GetUpdates - #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + #[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Serialize)] pub DeleteWebhook (DeleteWebhookSetters) => True { } diff --git a/src/payloads/edit_message_caption.rs b/src/payloads/edit_message_caption.rs index 725986b9..822ab78c 100644 --- a/src/payloads/edit_message_caption.rs +++ b/src/payloads/edit_message_caption.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_caption_inline.rs b/src/payloads/edit_message_caption_inline.rs index de4b1ebb..9cc64ad9 100644 --- a/src/payloads/edit_message_caption_inline.rs +++ b/src/payloads/edit_message_caption_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_live_location.rs b/src/payloads/edit_message_live_location.rs index 8d22ea0b..076cf7c6 100644 --- a/src/payloads/edit_message_live_location.rs +++ b/src/payloads/edit_message_live_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -26,8 +26,8 @@ impl_payload! { optional { /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/edit_message_live_location_inline.rs b/src/payloads/edit_message_live_location_inline.rs index cf26c12a..26d60b16 100644 --- a/src/payloads/edit_message_live_location_inline.rs +++ b/src/payloads/edit_message_live_location_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_media.rs b/src/payloads/edit_message_media.rs index 9fda6118..204ab1b0 100644 --- a/src/payloads/edit_message_media.rs +++ b/src/payloads/edit_message_media.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_media_inline.rs b/src/payloads/edit_message_media_inline.rs index bdc4fd53..e8dc7402 100644 --- a/src/payloads/edit_message_media_inline.rs +++ b/src/payloads/edit_message_media_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_reply_markup.rs b/src/payloads/edit_message_reply_markup.rs index 2103165e..c33a3d24 100644 --- a/src/payloads/edit_message_reply_markup.rs +++ b/src/payloads/edit_message_reply_markup.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_reply_markup_inline.rs b/src/payloads/edit_message_reply_markup_inline.rs index 439b85dc..507b55c0 100644 --- a/src/payloads/edit_message_reply_markup_inline.rs +++ b/src/payloads/edit_message_reply_markup_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_text.rs b/src/payloads/edit_message_text.rs index 94a1b7d4..fe366882 100644 --- a/src/payloads/edit_message_text.rs +++ b/src/payloads/edit_message_text.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_text_inline.rs b/src/payloads/edit_message_text_inline.rs index 33837594..7ae99cb0 100644 --- a/src/payloads/edit_message_text_inline.rs +++ b/src/payloads/edit_message_text_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/export_chat_invite_link.rs b/src/payloads/export_chat_invite_link.rs index 8ced7840..d754f18c 100644 --- a/src/payloads/export_chat_invite_link.rs +++ b/src/payloads/export_chat_invite_link.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/forward_message.rs b/src/payloads/forward_message.rs index 70fc8a19..45c09eb3 100644 --- a/src/payloads/forward_message.rs +++ b/src/payloads/forward_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat.rs b/src/payloads/get_chat.rs index cfc13aab..1cf35dab 100644 --- a/src/payloads/get_chat.rs +++ b/src/payloads/get_chat.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat_administrators.rs b/src/payloads/get_chat_administrators.rs index c0eae482..422727a0 100644 --- a/src/payloads/get_chat_administrators.rs +++ b/src/payloads/get_chat_administrators.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat_member.rs b/src/payloads/get_chat_member.rs index 008681c3..779c331a 100644 --- a/src/payloads/get_chat_member.rs +++ b/src/payloads/get_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat_members_count.rs b/src/payloads/get_chat_members_count.rs index 3223c5fa..7c1e17c0 100644 --- a/src/payloads/get_chat_members_count.rs +++ b/src/payloads/get_chat_members_count.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_file.rs b/src/payloads/get_file.rs index 3037bbe6..267e9d4b 100644 --- a/src/payloads/get_file.rs +++ b/src/payloads/get_file.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_me.rs b/src/payloads/get_me.rs index eef1103e..5cf408cf 100644 --- a/src/payloads/get_me.rs +++ b/src/payloads/get_me.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -9,7 +9,7 @@ impl_payload! { /// A simple method for testing your bot's auth token. Requires no parameters. Returns basic information about the bot in form of a [`User`] object. /// /// [`User`]: crate::types::User - #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + #[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Serialize)] pub GetMe (GetMeSetters) => User { } diff --git a/src/payloads/get_my_commands.rs b/src/payloads/get_my_commands.rs index a7ffb195..79759912 100644 --- a/src/payloads/get_my_commands.rs +++ b/src/payloads/get_my_commands.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -7,7 +7,7 @@ impl_payload! { /// Use this method to get the current list of the bot's commands. Requires no parameters. Returns Array of [`BotCommand`] on success. /// /// [`BotCommand`]: crate::types::BotCommand - #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + #[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Serialize)] pub GetMyCommands (GetMyCommandsSetters) => u32 { } diff --git a/src/payloads/get_sticker_set.rs b/src/payloads/get_sticker_set.rs index e0b061a1..9baec391 100644 --- a/src/payloads/get_sticker_set.rs +++ b/src/payloads/get_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_updates.rs b/src/payloads/get_updates.rs index d7484c8e..a9ab9fe1 100644 --- a/src/payloads/get_updates.rs +++ b/src/payloads/get_updates.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -8,9 +8,9 @@ use crate::types::{AllowedUpdate, Update}; impl_payload! { /// Use this method to receive incoming updates using long polling ([wiki]). An Array of [`Update`] objects is returned. /// - /// [`Update`]: crate::types::Update /// [wiki]: https://en.wikipedia.org/wiki/Push_technology#Long_polling - #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + /// [`Update`]: crate::types::Update + #[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Serialize)] pub GetUpdates (GetUpdatesSetters) => Vec { optional { diff --git a/src/payloads/get_user_profile_photos.rs b/src/payloads/get_user_profile_photos.rs index ac95ee08..73349590 100644 --- a/src/payloads/get_user_profile_photos.rs +++ b/src/payloads/get_user_profile_photos.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_webhook_info.rs b/src/payloads/get_webhook_info.rs index 9430960d..f885b9e3 100644 --- a/src/payloads/get_webhook_info.rs +++ b/src/payloads/get_webhook_info.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -10,7 +10,7 @@ impl_payload! { /// /// [`GetUpdates`]: crate::payloads::GetUpdates /// [`WebhookInfo`]: crate::types::WebhookInfo - #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + #[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Serialize)] pub GetWebhookInfo (GetWebhookInfoSetters) => WebhookInfo { } diff --git a/src/payloads/kick_chat_member.rs b/src/payloads/kick_chat_member.rs index decbc8bf..49eaf2a0 100644 --- a/src/payloads/kick_chat_member.rs +++ b/src/payloads/kick_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/leave_chat.rs b/src/payloads/leave_chat.rs index e3c05bad..8c00a049 100644 --- a/src/payloads/leave_chat.rs +++ b/src/payloads/leave_chat.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/mod.rs b/src/payloads/mod.rs index 3867431b..171588be 100644 --- a/src/payloads/mod.rs +++ b/src/payloads/mod.rs @@ -1,6 +1,7 @@ //! Payloads - data types sended to relegram -/// This module reexports all setters traits as `_`. When used with a glob import: +/// This module reexports all setters traits as `_`. When used with a glob +/// import: /// ``` /// use teloxide_core::payloads::setters::*; /// ``` diff --git a/src/payloads/pin_chat_message.rs b/src/payloads/pin_chat_message.rs index 43aff985..762c58de 100644 --- a/src/payloads/pin_chat_message.rs +++ b/src/payloads/pin_chat_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/promote_chat_member.rs b/src/payloads/promote_chat_member.rs index f79afc4e..2badffea 100644 --- a/src/payloads/promote_chat_member.rs +++ b/src/payloads/promote_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/restrict_chat_member.rs b/src/payloads/restrict_chat_member.rs index 3cce3797..c26fc645 100644 --- a/src/payloads/restrict_chat_member.rs +++ b/src/payloads/restrict_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_animation.rs b/src/payloads/send_animation.rs index 81741c9b..d351e9e8 100644 --- a/src/payloads/send_animation.rs +++ b/src/payloads/send_animation.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -44,8 +44,8 @@ impl_payload! { pub reply_to_message_id: i64, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_audio.rs b/src/payloads/send_audio.rs index 2992df9b..3aad0cfd 100644 --- a/src/payloads/send_audio.rs +++ b/src/payloads/send_audio.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -10,8 +10,8 @@ impl_payload! { /// /// For sending voice messages, use the [`SendVoice`] method instead. /// - /// [`SendVoice`]: crate::payloads::SendVoice /// [`Message`]: crate::types::Message + /// [`SendVoice`]: crate::payloads::SendVoice #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] pub SendAudio (SendAudioSetters) => Message { required { @@ -47,8 +47,8 @@ impl_payload! { pub reply_to_message_id: i64, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_chat_action.rs b/src/payloads/send_chat_action.rs index 9d012a7e..f7918b1b 100644 --- a/src/payloads/send_chat_action.rs +++ b/src/payloads/send_chat_action.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -20,13 +20,13 @@ impl_payload! { pub chat_id: ChatId [into], /// Type of action to broadcast. Choose one, depending on what the user is about to receive: typing for [text messages], upload_photo for [photos], record_video or upload_video for [videos], record_audio or upload_audio for [audio files], upload_document for [general files], find_location for [location data], record_video_note or upload_video_note for [video notes]. /// - /// [audio files]: crate::payloads::SendAudio - /// [video notes]: crate::payloads::SendVideoNote /// [videos]: crate::payloads::SendVideo - /// [location data]: crate::payloads::SendLocation /// [text messages]: crate::payloads::SendMessage - /// [general files]: crate::payloads::SendDocument + /// [video notes]: crate::payloads::SendVideoNote /// [photos]: crate::payloads::SendPhoto + /// [location data]: crate::payloads::SendLocation + /// [audio files]: crate::payloads::SendAudio + /// [general files]: crate::payloads::SendDocument pub action: ChatAction, } } diff --git a/src/payloads/send_contact.rs b/src/payloads/send_contact.rs index 3d3d75de..24b09d8a 100644 --- a/src/payloads/send_contact.rs +++ b/src/payloads/send_contact.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_dice.rs b/src/payloads/send_dice.rs index 6fb35e15..51df0b75 100644 --- a/src/payloads/send_dice.rs +++ b/src/payloads/send_dice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -26,8 +26,8 @@ impl_payload! { pub reply_to_message_id: i64, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_document.rs b/src/payloads/send_document.rs index 206281c7..c0317d9a 100644 --- a/src/payloads/send_document.rs +++ b/src/payloads/send_document.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_invoice.rs b/src/payloads/send_invoice.rs index 1b9afc4d..3cc12c5a 100644 --- a/src/payloads/send_invoice.rs +++ b/src/payloads/send_invoice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_location.rs b/src/payloads/send_location.rs index 45a988ce..8cb63f3b 100644 --- a/src/payloads/send_location.rs +++ b/src/payloads/send_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -32,8 +32,8 @@ impl_payload! { pub reply_to_message_id: i64, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_media_group.rs b/src/payloads/send_media_group.rs index 30a01a1e..77ebb879 100644 --- a/src/payloads/send_media_group.rs +++ b/src/payloads/send_media_group.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_message.rs b/src/payloads/send_message.rs index ece90beb..cff316d5 100644 --- a/src/payloads/send_message.rs +++ b/src/payloads/send_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -32,8 +32,8 @@ impl_payload! { pub reply_to_message_id: i64, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_photo.rs b/src/payloads/send_photo.rs index dc6ad339..3495d48a 100644 --- a/src/payloads/send_photo.rs +++ b/src/payloads/send_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_poll.rs b/src/payloads/send_poll.rs index 8011d77b..2d24d198 100644 --- a/src/payloads/send_poll.rs +++ b/src/payloads/send_poll.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_sticker.rs b/src/payloads/send_sticker.rs index 8a797b6e..6f102f33 100644 --- a/src/payloads/send_sticker.rs +++ b/src/payloads/send_sticker.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -28,8 +28,8 @@ impl_payload! { pub reply_to_message_id: i64, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_venue.rs b/src/payloads/send_venue.rs index f8394ec3..b8774605 100644 --- a/src/payloads/send_venue.rs +++ b/src/payloads/send_venue.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -36,8 +36,8 @@ impl_payload! { pub reply_to_message_id: i64, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_video.rs b/src/payloads/send_video.rs index a23bb980..bb704927 100644 --- a/src/payloads/send_video.rs +++ b/src/payloads/send_video.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -8,8 +8,8 @@ use crate::types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}; impl_payload! { /// Use this method to send video files, Telegram clients support mp4 videos (other formats may be sent as [`Document`]). On success, the sent [`Message`] is returned. Bots can currently send video files of up to 50 MB in size, this limit may be changed in the future. /// - /// [`Message`]: crate::types::Message /// [`Document`]: crate::types::Document + /// [`Message`]: crate::types::Message #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] pub SendVideo (SendVideoSetters) => Message { required { diff --git a/src/payloads/send_video_note.rs b/src/payloads/send_video_note.rs index 282c990a..eb02474f 100644 --- a/src/payloads/send_video_note.rs +++ b/src/payloads/send_video_note.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -37,8 +37,8 @@ impl_payload! { pub reply_to_message_id: i64, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_voice.rs b/src/payloads/send_voice.rs index 0a27dfa4..51291852 100644 --- a/src/payloads/send_voice.rs +++ b/src/payloads/send_voice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -8,9 +8,9 @@ use crate::types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}; impl_payload! { /// Use this method to send audio files, if you want Telegram clients to display the file as a playable voice message. For this to work, your audio must be in an .OGG file encoded with OPUS (other formats may be sent as [`Audio`] or [`Document`]). On success, the sent [`Message`] is returned. Bots can currently send voice messages of up to 50 MB in size, this limit may be changed in the future. /// + /// [`Audio`]: crate::types::Audio /// [`Message`]: crate::types::Message /// [`Document`]: crate::types::Document - /// [`Audio`]: crate::types::Audio #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] pub SendVoice (SendVoiceSetters) => Message { required { diff --git a/src/payloads/set_chat_administrator_custom_title.rs b/src/payloads/set_chat_administrator_custom_title.rs index 144f0f9d..684b83c0 100644 --- a/src/payloads/set_chat_administrator_custom_title.rs +++ b/src/payloads/set_chat_administrator_custom_title.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_description.rs b/src/payloads/set_chat_description.rs index f42c7908..f663d94a 100644 --- a/src/payloads/set_chat_description.rs +++ b/src/payloads/set_chat_description.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_permissions.rs b/src/payloads/set_chat_permissions.rs index 1b2e0ea4..fb942cd6 100644 --- a/src/payloads/set_chat_permissions.rs +++ b/src/payloads/set_chat_permissions.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_photo.rs b/src/payloads/set_chat_photo.rs index cbeeba58..afba0e27 100644 --- a/src/payloads/set_chat_photo.rs +++ b/src/payloads/set_chat_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_sticker_set.rs b/src/payloads/set_chat_sticker_set.rs index 42e0e8e8..36a7126d 100644 --- a/src/payloads/set_chat_sticker_set.rs +++ b/src/payloads/set_chat_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_title.rs b/src/payloads/set_chat_title.rs index 9c1982c7..bfb72e0d 100644 --- a/src/payloads/set_chat_title.rs +++ b/src/payloads/set_chat_title.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_my_commands.rs b/src/payloads/set_my_commands.rs index 4224b9f1..e8135915 100644 --- a/src/payloads/set_my_commands.rs +++ b/src/payloads/set_my_commands.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_passport_data_errors.rs b/src/payloads/set_passport_data_errors.rs index 686b63b4..88b93f47 100644 --- a/src/payloads/set_passport_data_errors.rs +++ b/src/payloads/set_passport_data_errors.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_sticker_position_in_set.rs b/src/payloads/set_sticker_position_in_set.rs index 05ef8b1c..84c18bb1 100644 --- a/src/payloads/set_sticker_position_in_set.rs +++ b/src/payloads/set_sticker_position_in_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_sticker_set_thumb.rs b/src/payloads/set_sticker_set_thumb.rs index a9cef002..c86afa76 100644 --- a/src/payloads/set_sticker_set_thumb.rs +++ b/src/payloads/set_sticker_set_thumb.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_webhook.rs b/src/payloads/set_webhook.rs index c3c8c53f..1870b93e 100644 --- a/src/payloads/set_webhook.rs +++ b/src/payloads/set_webhook.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/stop_message_live_location.rs b/src/payloads/stop_message_live_location.rs index bec8af47..b56d0790 100644 --- a/src/payloads/stop_message_live_location.rs +++ b/src/payloads/stop_message_live_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -10,8 +10,8 @@ impl_payload! { /// /// See also: [`StopMessageLiveLocationInline`](crate::payloads::StopMessageLiveLocationInline) /// - /// [`Message`]: crate::types::Message /// [`StopMessageLiveLocation`]: crate::payloads::StopMessageLiveLocation + /// [`Message`]: crate::types::Message #[derive(Debug, PartialEq, Clone, Serialize)] pub StopMessageLiveLocation (StopMessageLiveLocationSetters) => Message { required { diff --git a/src/payloads/stop_message_live_location_inline.rs b/src/payloads/stop_message_live_location_inline.rs index b9b7c40c..559a51ca 100644 --- a/src/payloads/stop_message_live_location_inline.rs +++ b/src/payloads/stop_message_live_location_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/stop_poll.rs b/src/payloads/stop_poll.rs index fc624c05..ba3475e8 100644 --- a/src/payloads/stop_poll.rs +++ b/src/payloads/stop_poll.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/unban_chat_member.rs b/src/payloads/unban_chat_member.rs index 83ba27d5..86e1fd19 100644 --- a/src/payloads/unban_chat_member.rs +++ b/src/payloads/unban_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/unpin_chat_message.rs b/src/payloads/unpin_chat_message.rs index dc6e35fe..e4cd1a78 100644 --- a/src/payloads/unpin_chat_message.rs +++ b/src/payloads/unpin_chat_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/upload_sticker_file.rs b/src/payloads/upload_sticker_file.rs index 46e8976e..54875c17 100644 --- a/src/payloads/upload_sticker_file.rs +++ b/src/payloads/upload_sticker_file.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (bc65e6f). +// This file is auto generated by `cg` (455146e). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; From 79b5c41bb5de8d6e1d9d3f68984386bc2f8f0114 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Sun, 15 Nov 2020 10:51:36 +0300 Subject: [PATCH 147/755] Apply suggestions from code review Co-authored-by: Temirkhan Myrzamadi --- src/local_macros.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/local_macros.rs b/src/local_macros.rs index 9f6947b3..971b2fa5 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -184,9 +184,9 @@ macro_rules! impl_payload { } impl $Method { - // We mirror telegram API and can't do anything with too many arguments + // We mirror Telegram API and can't do anything with too many arguments. #[allow(clippy::too_many_arguments)] - // It's just easier for macros to generate such code + // It's just easier for macros to generate such code. #[allow(clippy::redundant_field_names)] $vi fn new($($($fields : impl_payload!(@convert? $FTy $([$conv])?)),*)?) -> Self { Self { From 5d576455d9f49152f02e5dfa3a5580569b707672 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 15 Nov 2020 11:17:33 +0300 Subject: [PATCH 148/755] fix user_id param type --- src/payloads/add_sticker_to_set.rs | 4 ++-- src/payloads/answer_callback_query.rs | 4 ++-- src/payloads/answer_inline_query.rs | 4 ++-- src/payloads/answer_pre_checkout_query.rs | 2 +- src/payloads/answer_shipping_query.rs | 2 +- src/payloads/create_new_sticker_set.rs | 4 ++-- src/payloads/delete_chat_photo.rs | 2 +- src/payloads/delete_chat_sticker_set.rs | 2 +- src/payloads/delete_message.rs | 2 +- src/payloads/delete_sticker_from_set.rs | 2 +- src/payloads/delete_webhook.rs | 2 +- src/payloads/edit_message_caption.rs | 2 +- src/payloads/edit_message_caption_inline.rs | 2 +- src/payloads/edit_message_live_location.rs | 4 ++-- src/payloads/edit_message_live_location_inline.rs | 2 +- src/payloads/edit_message_media.rs | 2 +- src/payloads/edit_message_media_inline.rs | 2 +- src/payloads/edit_message_reply_markup.rs | 2 +- src/payloads/edit_message_reply_markup_inline.rs | 2 +- src/payloads/edit_message_text.rs | 2 +- src/payloads/edit_message_text_inline.rs | 2 +- src/payloads/export_chat_invite_link.rs | 2 +- src/payloads/forward_message.rs | 2 +- src/payloads/get_chat.rs | 2 +- src/payloads/get_chat_administrators.rs | 2 +- src/payloads/get_chat_member.rs | 4 ++-- src/payloads/get_chat_members_count.rs | 2 +- src/payloads/get_file.rs | 4 ++-- src/payloads/get_me.rs | 2 +- src/payloads/get_my_commands.rs | 2 +- src/payloads/get_sticker_set.rs | 2 +- src/payloads/get_updates.rs | 2 +- src/payloads/get_user_profile_photos.rs | 4 ++-- src/payloads/get_webhook_info.rs | 2 +- src/payloads/kick_chat_member.rs | 4 ++-- src/payloads/leave_chat.rs | 2 +- src/payloads/pin_chat_message.rs | 2 +- src/payloads/promote_chat_member.rs | 4 ++-- src/payloads/restrict_chat_member.rs | 4 ++-- src/payloads/send_animation.rs | 2 +- src/payloads/send_audio.rs | 4 ++-- src/payloads/send_chat_action.rs | 8 ++++---- src/payloads/send_contact.rs | 2 +- src/payloads/send_dice.rs | 2 +- src/payloads/send_document.rs | 2 +- src/payloads/send_invoice.rs | 2 +- src/payloads/send_location.rs | 2 +- src/payloads/send_media_group.rs | 2 +- src/payloads/send_message.rs | 2 +- src/payloads/send_photo.rs | 4 ++-- src/payloads/send_poll.rs | 2 +- src/payloads/send_sticker.rs | 4 ++-- src/payloads/send_venue.rs | 2 +- src/payloads/send_video.rs | 4 ++-- src/payloads/send_video_note.rs | 6 +++--- src/payloads/send_voice.rs | 2 +- src/payloads/set_chat_administrator_custom_title.rs | 4 ++-- src/payloads/set_chat_description.rs | 2 +- src/payloads/set_chat_permissions.rs | 2 +- src/payloads/set_chat_photo.rs | 2 +- src/payloads/set_chat_sticker_set.rs | 2 +- src/payloads/set_chat_title.rs | 2 +- src/payloads/set_my_commands.rs | 2 +- src/payloads/set_passport_data_errors.rs | 4 ++-- src/payloads/set_sticker_position_in_set.rs | 2 +- src/payloads/set_sticker_set_thumb.rs | 4 ++-- src/payloads/set_webhook.rs | 2 +- src/payloads/stop_message_live_location.rs | 4 ++-- src/payloads/stop_message_live_location_inline.rs | 4 ++-- src/payloads/stop_poll.rs | 2 +- src/payloads/unban_chat_member.rs | 4 ++-- src/payloads/unpin_chat_message.rs | 2 +- src/payloads/upload_sticker_file.rs | 4 ++-- 73 files changed, 100 insertions(+), 100 deletions(-) diff --git a/src/payloads/add_sticker_to_set.rs b/src/payloads/add_sticker_to_set.rs index 12a287cb..7836bd43 100644 --- a/src/payloads/add_sticker_to_set.rs +++ b/src/payloads/add_sticker_to_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -11,7 +11,7 @@ impl_payload! { pub AddStickerToSet (AddStickerToSetSetters) => True { required { /// User identifier of sticker file owner - pub user_id: u32, + pub user_id: i32, /// Sticker set name pub name: String [into], /// **PNG** or **TGS** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a _file\_id_ as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. [More info on Sending Files Âģ] diff --git a/src/payloads/answer_callback_query.rs b/src/payloads/answer_callback_query.rs index cc269c57..fda34ae4 100644 --- a/src/payloads/answer_callback_query.rs +++ b/src/payloads/answer_callback_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -28,8 +28,8 @@ impl_payload! { /// Otherwise, you may use links like `t.me/your\_bot?start=XXXX` that open your bot with a parameter. /// /// [@Botfather]: https://t.me/botfather - /// [callback_game]: https://core.telegram.org/bots/api#inlinekeyboardbutton /// [`Game`]: crate::types::Game + /// [callback_game]: https://core.telegram.org/bots/api#inlinekeyboardbutton pub url: String [into], /// The maximum amount of time in seconds that the result of the callback query may be cached client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0. pub cache_time: u32, diff --git a/src/payloads/answer_inline_query.rs b/src/payloads/answer_inline_query.rs index 3c41547b..7032695b 100644 --- a/src/payloads/answer_inline_query.rs +++ b/src/payloads/answer_inline_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -28,8 +28,8 @@ impl_payload! { /// /// _Example_: An inline bot that sends YouTube videos can ask the user to connect the bot to their YouTube account to adapt search results accordingly. To do this, it displays a 'Connect your YouTube account' button above the results, or even before showing any. The user presses the button, switches to a private chat with the bot and, in doing so, passes a start parameter that instructs the bot to return an oauth link. Once done, the bot can offer a [switch_inline] button so that the user can easily return to the chat where they wanted to use the bot's inline capabilities. /// - /// [Deep-linking]: https://core.telegram.org/bots#deep-linking /// [switch_inline]: https://core.telegram.org/bots/api#inlinekeyboardmarkup + /// [Deep-linking]: https://core.telegram.org/bots#deep-linking pub switch_pm_parameter: String [into], } } diff --git a/src/payloads/answer_pre_checkout_query.rs b/src/payloads/answer_pre_checkout_query.rs index e5a2b4ff..821bb4ae 100644 --- a/src/payloads/answer_pre_checkout_query.rs +++ b/src/payloads/answer_pre_checkout_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/answer_shipping_query.rs b/src/payloads/answer_shipping_query.rs index b04c3995..a4fc80f7 100644 --- a/src/payloads/answer_shipping_query.rs +++ b/src/payloads/answer_shipping_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/create_new_sticker_set.rs b/src/payloads/create_new_sticker_set.rs index 07d822c5..93f945c4 100644 --- a/src/payloads/create_new_sticker_set.rs +++ b/src/payloads/create_new_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -11,7 +11,7 @@ impl_payload! { pub CreateNewStickerSet (CreateNewStickerSetSetters) => True { required { /// User identifier of sticker file owner - pub user_id: u32, + pub user_id: i32, /// Short name of sticker set, to be used in `t.me/addstickers/` URLs (e.g., _animals_). Can contain only english letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in _“\_by\_”. _ is case insensitive. 1-64 characters. pub name: String [into], /// Sticker set title, 1-64 characters diff --git a/src/payloads/delete_chat_photo.rs b/src/payloads/delete_chat_photo.rs index 4a9672fd..dc447a48 100644 --- a/src/payloads/delete_chat_photo.rs +++ b/src/payloads/delete_chat_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_chat_sticker_set.rs b/src/payloads/delete_chat_sticker_set.rs index 0acad4c9..fef2f48b 100644 --- a/src/payloads/delete_chat_sticker_set.rs +++ b/src/payloads/delete_chat_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_message.rs b/src/payloads/delete_message.rs index a1eecabd..881b5003 100644 --- a/src/payloads/delete_message.rs +++ b/src/payloads/delete_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_sticker_from_set.rs b/src/payloads/delete_sticker_from_set.rs index faa1b962..ec8ca0fb 100644 --- a/src/payloads/delete_sticker_from_set.rs +++ b/src/payloads/delete_sticker_from_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_webhook.rs b/src/payloads/delete_webhook.rs index 22011bbf..3c6906af 100644 --- a/src/payloads/delete_webhook.rs +++ b/src/payloads/delete_webhook.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_caption.rs b/src/payloads/edit_message_caption.rs index 822ab78c..37f4e924 100644 --- a/src/payloads/edit_message_caption.rs +++ b/src/payloads/edit_message_caption.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_caption_inline.rs b/src/payloads/edit_message_caption_inline.rs index 9cc64ad9..fe94044f 100644 --- a/src/payloads/edit_message_caption_inline.rs +++ b/src/payloads/edit_message_caption_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_live_location.rs b/src/payloads/edit_message_live_location.rs index 076cf7c6..5e517259 100644 --- a/src/payloads/edit_message_live_location.rs +++ b/src/payloads/edit_message_live_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -26,8 +26,8 @@ impl_payload! { optional { /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/edit_message_live_location_inline.rs b/src/payloads/edit_message_live_location_inline.rs index 26d60b16..41bb41a8 100644 --- a/src/payloads/edit_message_live_location_inline.rs +++ b/src/payloads/edit_message_live_location_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_media.rs b/src/payloads/edit_message_media.rs index 204ab1b0..07b76417 100644 --- a/src/payloads/edit_message_media.rs +++ b/src/payloads/edit_message_media.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_media_inline.rs b/src/payloads/edit_message_media_inline.rs index e8dc7402..b5247819 100644 --- a/src/payloads/edit_message_media_inline.rs +++ b/src/payloads/edit_message_media_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_reply_markup.rs b/src/payloads/edit_message_reply_markup.rs index c33a3d24..90254d04 100644 --- a/src/payloads/edit_message_reply_markup.rs +++ b/src/payloads/edit_message_reply_markup.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_reply_markup_inline.rs b/src/payloads/edit_message_reply_markup_inline.rs index 507b55c0..0999db8a 100644 --- a/src/payloads/edit_message_reply_markup_inline.rs +++ b/src/payloads/edit_message_reply_markup_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_text.rs b/src/payloads/edit_message_text.rs index fe366882..b3d0dc7f 100644 --- a/src/payloads/edit_message_text.rs +++ b/src/payloads/edit_message_text.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_text_inline.rs b/src/payloads/edit_message_text_inline.rs index 7ae99cb0..d498d71c 100644 --- a/src/payloads/edit_message_text_inline.rs +++ b/src/payloads/edit_message_text_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/export_chat_invite_link.rs b/src/payloads/export_chat_invite_link.rs index d754f18c..d1d8ce6d 100644 --- a/src/payloads/export_chat_invite_link.rs +++ b/src/payloads/export_chat_invite_link.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/forward_message.rs b/src/payloads/forward_message.rs index 45c09eb3..29d627e8 100644 --- a/src/payloads/forward_message.rs +++ b/src/payloads/forward_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat.rs b/src/payloads/get_chat.rs index 1cf35dab..ce6d6b87 100644 --- a/src/payloads/get_chat.rs +++ b/src/payloads/get_chat.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat_administrators.rs b/src/payloads/get_chat_administrators.rs index 422727a0..169987f2 100644 --- a/src/payloads/get_chat_administrators.rs +++ b/src/payloads/get_chat_administrators.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat_member.rs b/src/payloads/get_chat_member.rs index 779c331a..22eb2aaf 100644 --- a/src/payloads/get_chat_member.rs +++ b/src/payloads/get_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -15,7 +15,7 @@ impl_payload! { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) pub chat_id: ChatId [into], /// Unique identifier of the target user - pub user_id: u32, + pub user_id: i32, } } } diff --git a/src/payloads/get_chat_members_count.rs b/src/payloads/get_chat_members_count.rs index 7c1e17c0..e353ff6d 100644 --- a/src/payloads/get_chat_members_count.rs +++ b/src/payloads/get_chat_members_count.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_file.rs b/src/payloads/get_file.rs index 267e9d4b..3dfdcfac 100644 --- a/src/payloads/get_file.rs +++ b/src/payloads/get_file.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -8,8 +8,8 @@ use crate::types::File; impl_payload! { /// Use this method to get basic info about a file and prepare it for downloading. For the moment, bots can download files of up to 20MB in size. On success, a [`File`] object is returned. The file can then be downloaded via the link `https://api.telegram.org/file/bot/`, where `` is taken from the response. 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 [`GetFile`] again. /// - /// [`GetFile`]: crate::payloads::GetFile /// [`File`]: crate::types::File + /// [`GetFile`]: crate::payloads::GetFile #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] pub GetFile (GetFileSetters) => File { required { diff --git a/src/payloads/get_me.rs b/src/payloads/get_me.rs index 5cf408cf..86cae760 100644 --- a/src/payloads/get_me.rs +++ b/src/payloads/get_me.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_my_commands.rs b/src/payloads/get_my_commands.rs index 79759912..405b6847 100644 --- a/src/payloads/get_my_commands.rs +++ b/src/payloads/get_my_commands.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_sticker_set.rs b/src/payloads/get_sticker_set.rs index 9baec391..10ff4917 100644 --- a/src/payloads/get_sticker_set.rs +++ b/src/payloads/get_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_updates.rs b/src/payloads/get_updates.rs index a9ab9fe1..514002da 100644 --- a/src/payloads/get_updates.rs +++ b/src/payloads/get_updates.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_user_profile_photos.rs b/src/payloads/get_user_profile_photos.rs index 73349590..590096e2 100644 --- a/src/payloads/get_user_profile_photos.rs +++ b/src/payloads/get_user_profile_photos.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -13,7 +13,7 @@ impl_payload! { pub GetUserProfilePhotos (GetUserProfilePhotosSetters) => UserProfilePhotos { required { /// Unique identifier of the target user - pub user_id: u32, + pub user_id: i32, } optional { /// Sequential number of the first photo to be returned. By default, all photos are returned. diff --git a/src/payloads/get_webhook_info.rs b/src/payloads/get_webhook_info.rs index f885b9e3..c342b2a0 100644 --- a/src/payloads/get_webhook_info.rs +++ b/src/payloads/get_webhook_info.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/kick_chat_member.rs b/src/payloads/kick_chat_member.rs index 49eaf2a0..835f33d3 100644 --- a/src/payloads/kick_chat_member.rs +++ b/src/payloads/kick_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -15,7 +15,7 @@ impl_payload! { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) pub chat_id: ChatId [into], /// Unique identifier of the target user - pub user_id: u32, + pub user_id: i32, } optional { /// Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever diff --git a/src/payloads/leave_chat.rs b/src/payloads/leave_chat.rs index 8c00a049..4c4ee3e2 100644 --- a/src/payloads/leave_chat.rs +++ b/src/payloads/leave_chat.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/pin_chat_message.rs b/src/payloads/pin_chat_message.rs index 762c58de..e1410799 100644 --- a/src/payloads/pin_chat_message.rs +++ b/src/payloads/pin_chat_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/promote_chat_member.rs b/src/payloads/promote_chat_member.rs index 2badffea..71c8cbeb 100644 --- a/src/payloads/promote_chat_member.rs +++ b/src/payloads/promote_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -13,7 +13,7 @@ impl_payload! { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) pub chat_id: ChatId [into], /// Unique identifier of the target user - pub user_id: u32, + pub user_id: i32, } optional { /// Pass True, if the administrator can change chat title, photo and other settings diff --git a/src/payloads/restrict_chat_member.rs b/src/payloads/restrict_chat_member.rs index c26fc645..81aa68ff 100644 --- a/src/payloads/restrict_chat_member.rs +++ b/src/payloads/restrict_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -13,7 +13,7 @@ impl_payload! { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) pub chat_id: ChatId [into], /// Unique identifier of the target user - pub user_id: u32, + pub user_id: i32, /// A JSON-serialized object for new user permissions pub permissions: ChatPermissions, } diff --git a/src/payloads/send_animation.rs b/src/payloads/send_animation.rs index d351e9e8..0868d053 100644 --- a/src/payloads/send_animation.rs +++ b/src/payloads/send_animation.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_audio.rs b/src/payloads/send_audio.rs index 3aad0cfd..711d8236 100644 --- a/src/payloads/send_audio.rs +++ b/src/payloads/send_audio.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -47,8 +47,8 @@ impl_payload! { pub reply_to_message_id: i64, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_chat_action.rs b/src/payloads/send_chat_action.rs index f7918b1b..d5a4a390 100644 --- a/src/payloads/send_chat_action.rs +++ b/src/payloads/send_chat_action.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -20,13 +20,13 @@ impl_payload! { pub chat_id: ChatId [into], /// Type of action to broadcast. Choose one, depending on what the user is about to receive: typing for [text messages], upload_photo for [photos], record_video or upload_video for [videos], record_audio or upload_audio for [audio files], upload_document for [general files], find_location for [location data], record_video_note or upload_video_note for [video notes]. /// + /// [audio files]: crate::payloads::SendAudio + /// [video notes]: crate::payloads::SendVideoNote /// [videos]: crate::payloads::SendVideo /// [text messages]: crate::payloads::SendMessage - /// [video notes]: crate::payloads::SendVideoNote /// [photos]: crate::payloads::SendPhoto - /// [location data]: crate::payloads::SendLocation - /// [audio files]: crate::payloads::SendAudio /// [general files]: crate::payloads::SendDocument + /// [location data]: crate::payloads::SendLocation pub action: ChatAction, } } diff --git a/src/payloads/send_contact.rs b/src/payloads/send_contact.rs index 24b09d8a..b3f4e8fd 100644 --- a/src/payloads/send_contact.rs +++ b/src/payloads/send_contact.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_dice.rs b/src/payloads/send_dice.rs index 51df0b75..8f750ee8 100644 --- a/src/payloads/send_dice.rs +++ b/src/payloads/send_dice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_document.rs b/src/payloads/send_document.rs index c0317d9a..8cf41457 100644 --- a/src/payloads/send_document.rs +++ b/src/payloads/send_document.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_invoice.rs b/src/payloads/send_invoice.rs index 3cc12c5a..1b0fe5fa 100644 --- a/src/payloads/send_invoice.rs +++ b/src/payloads/send_invoice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_location.rs b/src/payloads/send_location.rs index 8cb63f3b..9954d928 100644 --- a/src/payloads/send_location.rs +++ b/src/payloads/send_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_media_group.rs b/src/payloads/send_media_group.rs index 77ebb879..48bedf78 100644 --- a/src/payloads/send_media_group.rs +++ b/src/payloads/send_media_group.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_message.rs b/src/payloads/send_message.rs index cff316d5..96d6bcb0 100644 --- a/src/payloads/send_message.rs +++ b/src/payloads/send_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_photo.rs b/src/payloads/send_photo.rs index 3495d48a..726d9bc9 100644 --- a/src/payloads/send_photo.rs +++ b/src/payloads/send_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -34,8 +34,8 @@ impl_payload! { pub reply_to_message_id: i64, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_poll.rs b/src/payloads/send_poll.rs index 2d24d198..bc99ae45 100644 --- a/src/payloads/send_poll.rs +++ b/src/payloads/send_poll.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_sticker.rs b/src/payloads/send_sticker.rs index 6f102f33..3bdfd768 100644 --- a/src/payloads/send_sticker.rs +++ b/src/payloads/send_sticker.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -28,8 +28,8 @@ impl_payload! { pub reply_to_message_id: i64, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_venue.rs b/src/payloads/send_venue.rs index b8774605..87dce49e 100644 --- a/src/payloads/send_venue.rs +++ b/src/payloads/send_venue.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_video.rs b/src/payloads/send_video.rs index bb704927..5adf5aba 100644 --- a/src/payloads/send_video.rs +++ b/src/payloads/send_video.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -47,8 +47,8 @@ impl_payload! { pub reply_to_message_id: i64, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_video_note.rs b/src/payloads/send_video_note.rs index eb02474f..25281eec 100644 --- a/src/payloads/send_video_note.rs +++ b/src/payloads/send_video_note.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -8,8 +8,8 @@ use crate::types::{ChatId, InputFile, Message, ReplyMarkup}; impl_payload! { /// 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. On success, the sent [`Message`] is returned. /// - /// [`Message`]: crate::types::Message /// [v.4.0]: https://core.telegram.org/bots/api#document + /// [`Message`]: crate::types::Message #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] pub SendVideoNote (SendVideoNoteSetters) => Message { required { @@ -37,8 +37,8 @@ impl_payload! { pub reply_to_message_id: i64, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_voice.rs b/src/payloads/send_voice.rs index 51291852..a9315683 100644 --- a/src/payloads/send_voice.rs +++ b/src/payloads/send_voice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_administrator_custom_title.rs b/src/payloads/set_chat_administrator_custom_title.rs index 684b83c0..414ca72f 100644 --- a/src/payloads/set_chat_administrator_custom_title.rs +++ b/src/payloads/set_chat_administrator_custom_title.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -13,7 +13,7 @@ impl_payload! { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) pub chat_id: ChatId [into], /// Unique identifier of the target user - pub user_id: u32, + pub user_id: i32, /// New custom title for the administrator; 0-16 characters, emoji are not allowed pub custom_title: String [into], } diff --git a/src/payloads/set_chat_description.rs b/src/payloads/set_chat_description.rs index f663d94a..35b88b3f 100644 --- a/src/payloads/set_chat_description.rs +++ b/src/payloads/set_chat_description.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_permissions.rs b/src/payloads/set_chat_permissions.rs index fb942cd6..2c3a5cf9 100644 --- a/src/payloads/set_chat_permissions.rs +++ b/src/payloads/set_chat_permissions.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_photo.rs b/src/payloads/set_chat_photo.rs index afba0e27..f72c682f 100644 --- a/src/payloads/set_chat_photo.rs +++ b/src/payloads/set_chat_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_sticker_set.rs b/src/payloads/set_chat_sticker_set.rs index 36a7126d..9c09328e 100644 --- a/src/payloads/set_chat_sticker_set.rs +++ b/src/payloads/set_chat_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_title.rs b/src/payloads/set_chat_title.rs index bfb72e0d..76b72b8d 100644 --- a/src/payloads/set_chat_title.rs +++ b/src/payloads/set_chat_title.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_my_commands.rs b/src/payloads/set_my_commands.rs index e8135915..4916fb6a 100644 --- a/src/payloads/set_my_commands.rs +++ b/src/payloads/set_my_commands.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_passport_data_errors.rs b/src/payloads/set_passport_data_errors.rs index 88b93f47..74ab729a 100644 --- a/src/payloads/set_passport_data_errors.rs +++ b/src/payloads/set_passport_data_errors.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -13,7 +13,7 @@ impl_payload! { pub SetPassportDataErrors (SetPassportDataErrorsSetters) => True { required { /// User identifier - pub user_id: u32, + pub user_id: i32, /// A JSON-serialized array describing the errors pub errors: Vec [collect], } diff --git a/src/payloads/set_sticker_position_in_set.rs b/src/payloads/set_sticker_position_in_set.rs index 84c18bb1..80fad985 100644 --- a/src/payloads/set_sticker_position_in_set.rs +++ b/src/payloads/set_sticker_position_in_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_sticker_set_thumb.rs b/src/payloads/set_sticker_set_thumb.rs index c86afa76..b9301535 100644 --- a/src/payloads/set_sticker_set_thumb.rs +++ b/src/payloads/set_sticker_set_thumb.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -13,7 +13,7 @@ impl_payload! { /// Name of the sticker set pub name: String [into], /// User identifier of sticker file owner - pub user_id: u32, + pub user_id: i32, } optional { /// A **PNG** image with the thumbnail, must be up to 128 kilobytes in size and have width and height exactly 100px, or a **TGS** animation with the thumbnail up to 32 kilobytes in size; see https://core.telegram.org/animated_stickers#technical-requirements for animated sticker technical requirements. Pass a _file\_id_ as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. [More info on Sending Files Âģ]. Animated sticker set thumbnail can't be uploaded via HTTP URL. diff --git a/src/payloads/set_webhook.rs b/src/payloads/set_webhook.rs index 1870b93e..b04b5bbc 100644 --- a/src/payloads/set_webhook.rs +++ b/src/payloads/set_webhook.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/stop_message_live_location.rs b/src/payloads/stop_message_live_location.rs index b56d0790..4e337d38 100644 --- a/src/payloads/stop_message_live_location.rs +++ b/src/payloads/stop_message_live_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -27,8 +27,8 @@ impl_payload! { optional { /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/stop_message_live_location_inline.rs b/src/payloads/stop_message_live_location_inline.rs index 559a51ca..9b0813be 100644 --- a/src/payloads/stop_message_live_location_inline.rs +++ b/src/payloads/stop_message_live_location_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -24,8 +24,8 @@ impl_payload! { optional { /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/stop_poll.rs b/src/payloads/stop_poll.rs index ba3475e8..a12c9a01 100644 --- a/src/payloads/stop_poll.rs +++ b/src/payloads/stop_poll.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/unban_chat_member.rs b/src/payloads/unban_chat_member.rs index 86e1fd19..cd9e4fde 100644 --- a/src/payloads/unban_chat_member.rs +++ b/src/payloads/unban_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -13,7 +13,7 @@ impl_payload! { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) pub chat_id: ChatId [into], /// Unique identifier of the target user - pub user_id: u32, + pub user_id: i32, } } } diff --git a/src/payloads/unpin_chat_message.rs b/src/payloads/unpin_chat_message.rs index e4cd1a78..dc78f594 100644 --- a/src/payloads/unpin_chat_message.rs +++ b/src/payloads/unpin_chat_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/upload_sticker_file.rs b/src/payloads/upload_sticker_file.rs index 54875c17..908f50a3 100644 --- a/src/payloads/upload_sticker_file.rs +++ b/src/payloads/upload_sticker_file.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (455146e). +// This file is auto generated by `cg` (e634f65). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -11,7 +11,7 @@ impl_payload! { pub UploadStickerFile (UploadStickerFileSetters) => File { required { /// User identifier of sticker file owner - pub user_id: u32, + pub user_id: i32, /// PNG image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. [More info on Sending Files Âģ] /// /// [More info on Sending Files Âģ]: crate::types::InputFile From 40bbfb05ea16a3e04efe10f461210c943545aa3e Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 16 Nov 2020 02:48:24 +0300 Subject: [PATCH 149/755] fixup SendInvice::chat_id type --- src/payloads/send_invoice.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/payloads/send_invoice.rs b/src/payloads/send_invoice.rs index 1b0fe5fa..c9069c1f 100644 --- a/src/payloads/send_invoice.rs +++ b/src/payloads/send_invoice.rs @@ -13,7 +13,7 @@ impl_payload! { pub SendInvoice (SendInvoiceSetters) => Message { required { /// Unique identifier for the target private chat - pub chat_id: u32, + pub chat_id: i32, /// Product name, 1-32 characters pub title: String [into], /// Product description, 1-255 characters From 19c375f5e01649c47f653792f4339b9f5d1d2a59 Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 16 Nov 2020 12:58:55 +0300 Subject: [PATCH 150/755] fix message_id type --- src/payloads/delete_message.rs | 2 +- src/payloads/edit_message_caption.rs | 2 +- src/payloads/edit_message_live_location.rs | 2 +- src/payloads/edit_message_media.rs | 2 +- src/payloads/edit_message_reply_markup.rs | 2 +- src/payloads/edit_message_text.rs | 2 +- src/payloads/forward_message.rs | 2 +- src/payloads/pin_chat_message.rs | 2 +- src/payloads/send_animation.rs | 2 +- src/payloads/send_audio.rs | 2 +- src/payloads/send_contact.rs | 2 +- src/payloads/send_dice.rs | 2 +- src/payloads/send_document.rs | 2 +- src/payloads/send_invoice.rs | 2 +- src/payloads/send_location.rs | 2 +- src/payloads/send_media_group.rs | 2 +- src/payloads/send_message.rs | 2 +- src/payloads/send_photo.rs | 2 +- src/payloads/send_poll.rs | 2 +- src/payloads/send_sticker.rs | 2 +- src/payloads/send_venue.rs | 2 +- src/payloads/send_video.rs | 2 +- src/payloads/send_video_note.rs | 2 +- src/payloads/send_voice.rs | 2 +- src/payloads/stop_message_live_location.rs | 2 +- src/payloads/stop_poll.rs | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/payloads/delete_message.rs b/src/payloads/delete_message.rs index 881b5003..cc94d937 100644 --- a/src/payloads/delete_message.rs +++ b/src/payloads/delete_message.rs @@ -22,7 +22,7 @@ impl_payload! { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`). pub chat_id: ChatId [into], /// Identifier of the message to delete - pub message_id: i64, + pub message_id: i32, } } } diff --git a/src/payloads/edit_message_caption.rs b/src/payloads/edit_message_caption.rs index 37f4e924..aafa4f69 100644 --- a/src/payloads/edit_message_caption.rs +++ b/src/payloads/edit_message_caption.rs @@ -15,7 +15,7 @@ impl_payload! { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`). pub chat_id: ChatId [into], /// Identifier of the message to edit - pub message_id: i64, + pub message_id: i32, /// New caption of the message, 0-1024 characters after entities parsing pub caption: String [into], } diff --git a/src/payloads/edit_message_live_location.rs b/src/payloads/edit_message_live_location.rs index 5e517259..727f46d4 100644 --- a/src/payloads/edit_message_live_location.rs +++ b/src/payloads/edit_message_live_location.rs @@ -17,7 +17,7 @@ impl_payload! { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) pub chat_id: ChatId [into], /// Identifier of the message to edit - pub message_id: i64, + pub message_id: i32, /// Latitude of new location pub latitude: f64, /// Longitude of new location diff --git a/src/payloads/edit_message_media.rs b/src/payloads/edit_message_media.rs index 07b76417..5e687339 100644 --- a/src/payloads/edit_message_media.rs +++ b/src/payloads/edit_message_media.rs @@ -15,7 +15,7 @@ impl_payload! { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`). pub chat_id: ChatId [into], /// Identifier of the message to edit - pub message_id: i64, + pub message_id: i32, /// A JSON-serialized object for a new media content of the message pub media: InputMedia, } diff --git a/src/payloads/edit_message_reply_markup.rs b/src/payloads/edit_message_reply_markup.rs index 90254d04..4deafeb4 100644 --- a/src/payloads/edit_message_reply_markup.rs +++ b/src/payloads/edit_message_reply_markup.rs @@ -15,7 +15,7 @@ impl_payload! { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`). pub chat_id: ChatId [into], /// Identifier of the message to edit - pub message_id: i64, + pub message_id: i32, } optional { /// A JSON-serialized object for an [inline keyboard]. diff --git a/src/payloads/edit_message_text.rs b/src/payloads/edit_message_text.rs index b3d0dc7f..00a8aa72 100644 --- a/src/payloads/edit_message_text.rs +++ b/src/payloads/edit_message_text.rs @@ -17,7 +17,7 @@ impl_payload! { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`). pub chat_id: ChatId [into], /// Identifier of the message to edit - pub message_id: i64, + pub message_id: i32, /// New text of the message, 1-4096 characters after entities parsing pub text: String [into], } diff --git a/src/payloads/forward_message.rs b/src/payloads/forward_message.rs index 29d627e8..336004e6 100644 --- a/src/payloads/forward_message.rs +++ b/src/payloads/forward_message.rs @@ -17,7 +17,7 @@ impl_payload! { /// Unique identifier for the chat where the original message was sent (or channel username in the format `@channelusername`) pub from_chat_id: ChatId [into], /// Message identifier in the chat specified in _from\_chat\_id_ - pub message_id: i64, + pub message_id: i32, } optional { /// Sends the message [silently]. Users will receive a notification with no sound. diff --git a/src/payloads/pin_chat_message.rs b/src/payloads/pin_chat_message.rs index e1410799..73b03b5f 100644 --- a/src/payloads/pin_chat_message.rs +++ b/src/payloads/pin_chat_message.rs @@ -13,7 +13,7 @@ impl_payload! { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) pub chat_id: ChatId [into], /// Identifier of a message to pin - pub message_id: i64, + pub message_id: i32, } optional { /// Pass True, if it is not necessary to send a notification to all chat members about the new pinned message. Notifications are always disabled in channels. diff --git a/src/payloads/send_animation.rs b/src/payloads/send_animation.rs index 0868d053..168c5681 100644 --- a/src/payloads/send_animation.rs +++ b/src/payloads/send_animation.rs @@ -41,7 +41,7 @@ impl_payload! { /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, /// If the message is a reply, ID of the original message - pub reply_to_message_id: i64, + pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/src/payloads/send_audio.rs b/src/payloads/send_audio.rs index 711d8236..6c7e636c 100644 --- a/src/payloads/send_audio.rs +++ b/src/payloads/send_audio.rs @@ -44,7 +44,7 @@ impl_payload! { /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, /// If the message is a reply, ID of the original message - pub reply_to_message_id: i64, + pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/src/payloads/send_contact.rs b/src/payloads/send_contact.rs index b3f4e8fd..6980c662 100644 --- a/src/payloads/send_contact.rs +++ b/src/payloads/send_contact.rs @@ -31,7 +31,7 @@ impl_payload! { /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, /// If the message is a reply, ID of the original message - pub reply_to_message_id: i64, + pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/src/payloads/send_dice.rs b/src/payloads/send_dice.rs index 8f750ee8..755b2b2f 100644 --- a/src/payloads/send_dice.rs +++ b/src/payloads/send_dice.rs @@ -23,7 +23,7 @@ impl_payload! { /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, /// If the message is a reply, ID of the original message - pub reply_to_message_id: i64, + pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/src/payloads/send_document.rs b/src/payloads/send_document.rs index 8cf41457..2d66f28e 100644 --- a/src/payloads/send_document.rs +++ b/src/payloads/send_document.rs @@ -35,7 +35,7 @@ impl_payload! { /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, /// If the message is a reply, ID of the original message - pub reply_to_message_id: i64, + pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/src/payloads/send_invoice.rs b/src/payloads/send_invoice.rs index c9069c1f..292cb81a 100644 --- a/src/payloads/send_invoice.rs +++ b/src/payloads/send_invoice.rs @@ -61,7 +61,7 @@ impl_payload! { /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, /// If the message is a reply, ID of the original message - pub reply_to_message_id: i64, + pub reply_to_message_id: i32, /// A JSON-serialized object for an [inline keyboard]. If empty, one 'Pay `total price`' button will be shown. If not empty, the first button must be a Pay button. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/src/payloads/send_location.rs b/src/payloads/send_location.rs index 9954d928..fbaef731 100644 --- a/src/payloads/send_location.rs +++ b/src/payloads/send_location.rs @@ -29,7 +29,7 @@ impl_payload! { /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, /// If the message is a reply, ID of the original message - pub reply_to_message_id: i64, + pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/src/payloads/send_media_group.rs b/src/payloads/send_media_group.rs index 48bedf78..3799557d 100644 --- a/src/payloads/send_media_group.rs +++ b/src/payloads/send_media_group.rs @@ -23,7 +23,7 @@ impl_payload! { /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, /// If the message is a reply, ID of the original message - pub reply_to_message_id: i64, + pub reply_to_message_id: i32, } } } diff --git a/src/payloads/send_message.rs b/src/payloads/send_message.rs index 96d6bcb0..a4f30955 100644 --- a/src/payloads/send_message.rs +++ b/src/payloads/send_message.rs @@ -29,7 +29,7 @@ impl_payload! { /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, /// If the message is a reply, ID of the original message - pub reply_to_message_id: i64, + pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/src/payloads/send_photo.rs b/src/payloads/send_photo.rs index 726d9bc9..10eeb698 100644 --- a/src/payloads/send_photo.rs +++ b/src/payloads/send_photo.rs @@ -31,7 +31,7 @@ impl_payload! { /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, /// If the message is a reply, ID of the original message - pub reply_to_message_id: i64, + pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/src/payloads/send_poll.rs b/src/payloads/send_poll.rs index bc99ae45..59f96801 100644 --- a/src/payloads/send_poll.rs +++ b/src/payloads/send_poll.rs @@ -45,7 +45,7 @@ impl_payload! { /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, /// If the message is a reply, ID of the original message - pub reply_to_message_id: i64, + pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/src/payloads/send_sticker.rs b/src/payloads/send_sticker.rs index 3bdfd768..fddf0ddb 100644 --- a/src/payloads/send_sticker.rs +++ b/src/payloads/send_sticker.rs @@ -25,7 +25,7 @@ impl_payload! { /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, /// If the message is a reply, ID of the original message - pub reply_to_message_id: i64, + pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/src/payloads/send_venue.rs b/src/payloads/send_venue.rs index 87dce49e..355806d1 100644 --- a/src/payloads/send_venue.rs +++ b/src/payloads/send_venue.rs @@ -33,7 +33,7 @@ impl_payload! { /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, /// If the message is a reply, ID of the original message - pub reply_to_message_id: i64, + pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/src/payloads/send_video.rs b/src/payloads/send_video.rs index 5adf5aba..c4b40121 100644 --- a/src/payloads/send_video.rs +++ b/src/payloads/send_video.rs @@ -44,7 +44,7 @@ impl_payload! { /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, /// If the message is a reply, ID of the original message - pub reply_to_message_id: i64, + pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/src/payloads/send_video_note.rs b/src/payloads/send_video_note.rs index 25281eec..47d5808a 100644 --- a/src/payloads/send_video_note.rs +++ b/src/payloads/send_video_note.rs @@ -34,7 +34,7 @@ impl_payload! { /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, /// If the message is a reply, ID of the original message - pub reply_to_message_id: i64, + pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/src/payloads/send_voice.rs b/src/payloads/send_voice.rs index a9315683..bcea5f38 100644 --- a/src/payloads/send_voice.rs +++ b/src/payloads/send_voice.rs @@ -35,7 +35,7 @@ impl_payload! { /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, /// If the message is a reply, ID of the original message - pub reply_to_message_id: i64, + pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/src/payloads/stop_message_live_location.rs b/src/payloads/stop_message_live_location.rs index 4e337d38..60c35d58 100644 --- a/src/payloads/stop_message_live_location.rs +++ b/src/payloads/stop_message_live_location.rs @@ -18,7 +18,7 @@ impl_payload! { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) pub chat_id: ChatId [into], /// Identifier of the message to edit - pub message_id: i64, + pub message_id: i32, /// Latitude of new location pub latitude: f64, /// Longitude of new location diff --git a/src/payloads/stop_poll.rs b/src/payloads/stop_poll.rs index a12c9a01..b0abacbb 100644 --- a/src/payloads/stop_poll.rs +++ b/src/payloads/stop_poll.rs @@ -13,7 +13,7 @@ impl_payload! { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`). pub chat_id: ChatId [into], /// Identifier of the message to edit - pub message_id: i64, + pub message_id: i32, } optional { /// A JSON-serialized object for an [inline keyboard]. From fa0e86c547b804c42d41596d18cf2101fe1cb023 Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 16 Nov 2020 14:14:00 +0300 Subject: [PATCH 151/755] Add internal ability to change API url Recently, telegram has published [Telegram Bot API Server] - HTTP API <-> MTPROTO proxy which is used internally by telegram. Now it's possible to run the server locally. However currently it's not possible to use teloxide with such local server, because API url is hardcoded into teloxide. This commit makes _internall_ changes to allow setting the API url. It doesn't yet allow to set the url from user code (i.e.: no changes in public API), in my opinion such additions are blocked on some bot refactorings. `Bot::api_url` is made `Option>` to minimize costs in case of 'default url' (probably the most common case). Progress of this issue is tracked in https://github.com/teloxide/teloxide/issues/317 [Telegram Bot API Server]: https://github.com/tdlib/telegram-bot-api --- src/bot/mod.rs | 18 +++++++++++++----- src/net/request.rs | 6 ++++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 5c46e992..56ef13b3 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -27,6 +27,7 @@ pub(crate) const TELOXIDE_PROXY: &str = "TELOXIDE_PROXY"; #[derive(Debug, Clone)] pub struct Bot { token: Arc, + api_url: Option>, client: Client, parse_mode: Option, } @@ -102,6 +103,7 @@ impl Bot { { Self { token: Into::>::into(Into::::into(token)), + api_url: None, client, parse_mode: None, } @@ -119,13 +121,16 @@ impl Bot { { let client = self.client.clone(); let token = Arc::clone(&self.token); + let api_url = self.api_url.clone(); let params = serde_json::to_vec(payload) // this `expect` should be ok since we don't write request those may trigger error here .expect("serialization of request to be infallible"); - // async move to capture client&token - async move { net::request_json2(&client, token.as_ref(), P::NAME, params).await } + // async move to capture client&token&api_url¶ms + async move { + net::request_json2(&client, token.as_ref(), api_url.as_deref(), P::NAME, params).await + } } pub(crate) fn execute_multipart

( @@ -138,13 +143,15 @@ impl Bot { { let client = self.client.clone(); let token = Arc::clone(&self.token); + let api_url = self.api_url.clone(); let params = serde_multipart::to_form(payload); - // async move to capture client&token¶ms + // async move to capture client&token&api_url¶ms async move { let params = params.await?; - net::request_multipart2(&client, token.as_ref(), P::NAME, params).await + net::request_multipart2(&client, token.as_ref(), api_url.as_deref(), P::NAME, params) + .await } } } @@ -281,8 +288,9 @@ impl BotBuilder { #[must_use] pub fn build(self) -> Bot { Bot { - client: self.client.unwrap_or_else(crate::client_from_env), token: self.token.unwrap_or_else(|| get_env(TELOXIDE_TOKEN)).into(), + api_url: None, + client: self.client.unwrap_or_else(crate::client_from_env), parse_mode: self.parse_mode, } } diff --git a/src/net/request.rs b/src/net/request.rs index 824fa03c..6a2a65b1 100644 --- a/src/net/request.rs +++ b/src/net/request.rs @@ -72,6 +72,7 @@ where pub async fn request_multipart2( client: &Client, token: &str, + api_url: Option<&str>, method_name: &str, params: reqwest::multipart::Form, ) -> ResponseResult @@ -79,7 +80,7 @@ where T: DeserializeOwned, { let response = client - .post(&super::method_url(TELEGRAM_API_URL, token, method_name)) + .post(&super::method_url(api_url.unwrap_or(TELEGRAM_API_URL), token, method_name)) .multipart(params) .send() .await @@ -91,6 +92,7 @@ where pub async fn request_json2( client: &Client, token: &str, + api_url: Option<&str>, method_name: &str, params: Vec, ) -> ResponseResult @@ -98,7 +100,7 @@ where T: DeserializeOwned, { let response = client - .post(&super::method_url(TELEGRAM_API_URL, token, method_name)) + .post(&super::method_url(api_url.unwrap_or(TELEGRAM_API_URL), token, method_name)) .header(CONTENT_TYPE, HeaderValue::from_static("application/json")) .body(params) .send() From 17b44db578ab7adaca3971f284f53c43a98d6d28 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 17 Nov 2020 22:38:30 +0300 Subject: [PATCH 152/755] Add `ApiUrl` Add `ApiUrl` and also use `Url` instead of `&str` for urls to clean code --- src/bot/api_url.rs | 17 +++++++++++++++++ src/bot/mod.rs | 15 +++++++-------- src/net/download.rs | 14 ++++++++++---- src/net/mod.rs | 22 ++++++++++++---------- src/net/request.rs | 20 ++++++++++++++------ 5 files changed, 60 insertions(+), 28 deletions(-) create mode 100644 src/bot/api_url.rs diff --git a/src/bot/api_url.rs b/src/bot/api_url.rs new file mode 100644 index 00000000..8c33a38f --- /dev/null +++ b/src/bot/api_url.rs @@ -0,0 +1,17 @@ +#[derive(Debug, Clone)] +pub(crate) enum ApiUrl { + Default, + #[allow(dead_code)] + Custom(reqwest::Url), +} + +impl ApiUrl { + pub(crate) fn get(&self) -> reqwest::Url { + match self { + // FIXME(waffle): parse once + ApiUrl::Default => reqwest::Url::parse(crate::net::TELEGRAM_API_URL) + .expect("failed to parse default url"), + ApiUrl::Custom(url) => url.clone(), + } + } +} diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 56ef13b3..bdba198b 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -7,6 +7,7 @@ use reqwest::{ use serde::{de::DeserializeOwned, Serialize}; use crate::{ + bot::api_url::ApiUrl, net, requests::{Payload, ResponseResult}, serde_multipart, @@ -14,6 +15,7 @@ use crate::{ }; mod api; +mod api_url; mod download; pub(crate) const TELOXIDE_TOKEN: &str = "TELOXIDE_TOKEN"; @@ -27,7 +29,7 @@ pub(crate) const TELOXIDE_PROXY: &str = "TELOXIDE_PROXY"; #[derive(Debug, Clone)] pub struct Bot { token: Arc, - api_url: Option>, + api_url: ApiUrl, client: Client, parse_mode: Option, } @@ -103,7 +105,7 @@ impl Bot { { Self { token: Into::>::into(Into::::into(token)), - api_url: None, + api_url: ApiUrl::Default, client, parse_mode: None, } @@ -128,9 +130,7 @@ impl Bot { .expect("serialization of request to be infallible"); // async move to capture client&token&api_url¶ms - async move { - net::request_json2(&client, token.as_ref(), api_url.as_deref(), P::NAME, params).await - } + async move { net::request_json2(&client, token.as_ref(), api_url.get(), P::NAME, params).await } } pub(crate) fn execute_multipart

( @@ -150,8 +150,7 @@ impl Bot { // async move to capture client&token&api_url¶ms async move { let params = params.await?; - net::request_multipart2(&client, token.as_ref(), api_url.as_deref(), P::NAME, params) - .await + net::request_multipart2(&client, token.as_ref(), api_url.get(), P::NAME, params).await } } } @@ -289,7 +288,7 @@ impl BotBuilder { pub fn build(self) -> Bot { Bot { token: self.token.unwrap_or_else(|| get_env(TELOXIDE_TOKEN)).into(), - api_url: None, + api_url: ApiUrl::Default, client: self.client.unwrap_or_else(crate::client_from_env), parse_mode: self.parse_mode, } diff --git a/src/net/download.rs b/src/net/download.rs index aa54f4f0..3a507ad2 100644 --- a/src/net/download.rs +++ b/src/net/download.rs @@ -3,8 +3,6 @@ use tokio::io::{AsyncWrite, AsyncWriteExt}; use crate::errors::DownloadError; -use super::TELEGRAM_API_URL; - pub async fn download_file( client: &Client, token: &str, @@ -15,7 +13,11 @@ where D: AsyncWrite + Unpin, { let mut res = client - .get(&super::file_url(TELEGRAM_API_URL, token, path)) + .get(crate::net::file_url( + reqwest::Url::parse(crate::net::TELEGRAM_API_URL).expect("failed to parse default url"), + token, + path, + )) .send() .await? .error_for_status()?; @@ -33,7 +35,11 @@ pub async fn download_file_stream( path: &str, ) -> Result>, reqwest::Error> { let res = client - .get(&super::file_url(TELEGRAM_API_URL, token, path)) + .get(crate::net::file_url( + reqwest::Url::parse(crate::net::TELEGRAM_API_URL).expect("failed to parse default url"), + token, + path, + )) .send() .await? .error_for_status()?; diff --git a/src/net/mod.rs b/src/net/mod.rs index ede6cc0b..88f857cb 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -8,36 +8,38 @@ mod download; mod request; mod telegram_response; -const TELEGRAM_API_URL: &str = "https://api.telegram.org"; +pub(crate) const TELEGRAM_API_URL: &str = "https://api.telegram.org"; /// Creates URL for making HTTPS requests. See the [Telegram documentation]. /// /// [Telegram documentation]: https://core.telegram.org/bots/api#making-requests -fn method_url(base: &str, token: &str, method_name: &str) -> String { - format!("{url}/bot{token}/{method}", url = base, token = token, method = method_name) +fn method_url(base: reqwest::Url, token: &str, method_name: &str) -> reqwest::Url { + base.join(&format!("/bot{token}/{method}", token = token, method = method_name)) + .expect("failed to format url") } /// Creates URL for downloading a file. See the [Telegram documentation]. /// /// [Telegram documentation]: https://core.telegram.org/bots/api#file -fn file_url(base: &str, token: &str, file_path: &str) -> String { - format!("{url}/file/bot{token}/{file}", url = base, token = token, file = file_path,) +fn file_url(base: reqwest::Url, token: &str, file_path: &str) -> reqwest::Url { + base.join(&format!("file/bot{token}/{file}", token = token, file = file_path)) + .expect("failed to format url") } #[cfg(test)] mod tests { - use super::*; + use crate::net::*; #[test] fn method_url_test() { let url = method_url( - TELEGRAM_API_URL, + reqwest::Url::parse(TELEGRAM_API_URL).unwrap(), "535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao", "methodName", ); assert_eq!( - url, + url.as_str(), "https://api.telegram.org/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/methodName" ); } @@ -45,13 +47,13 @@ mod tests { #[test] fn file_url_test() { let url = file_url( - TELEGRAM_API_URL, + reqwest::Url::parse(TELEGRAM_API_URL).unwrap(), "535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao", "AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ", ); assert_eq!( - url, + url.as_str(), "https://api.telegram.org/file/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ" ); } diff --git a/src/net/request.rs b/src/net/request.rs index 6a2a65b1..887199e5 100644 --- a/src/net/request.rs +++ b/src/net/request.rs @@ -35,7 +35,11 @@ where }; let response = client - .post(&super::method_url(TELEGRAM_API_URL, token, method_name)) + .post(crate::net::method_url( + reqwest::Url::parse(TELEGRAM_API_URL).expect("failed to parse default url"), + token, + method_name, + )) .multipart(form) .send() .await @@ -55,7 +59,11 @@ where R: DeserializeOwned, { let response = client - .post(&super::method_url(TELEGRAM_API_URL, token, method_name)) + .post(crate::net::method_url( + reqwest::Url::parse(TELEGRAM_API_URL).expect("failed to parse default url"), + token, + method_name, + )) .json(params) .send() .await @@ -72,7 +80,7 @@ where pub async fn request_multipart2( client: &Client, token: &str, - api_url: Option<&str>, + api_url: reqwest::Url, method_name: &str, params: reqwest::multipart::Form, ) -> ResponseResult @@ -80,7 +88,7 @@ where T: DeserializeOwned, { let response = client - .post(&super::method_url(api_url.unwrap_or(TELEGRAM_API_URL), token, method_name)) + .post(crate::net::method_url(api_url, token, method_name)) .multipart(params) .send() .await @@ -92,7 +100,7 @@ where pub async fn request_json2( client: &Client, token: &str, - api_url: Option<&str>, + api_url: reqwest::Url, method_name: &str, params: Vec, ) -> ResponseResult @@ -100,7 +108,7 @@ where T: DeserializeOwned, { let response = client - .post(&super::method_url(api_url.unwrap_or(TELEGRAM_API_URL), token, method_name)) + .post(crate::net::method_url(api_url, token, method_name)) .header(CONTENT_TYPE, HeaderValue::from_static("application/json")) .body(params) .send() From 05582de2fdea1e6132237c3de67afe22ab5cdf70 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 17 Nov 2020 22:43:34 +0300 Subject: [PATCH 153/755] Add fixme to #[allow] attr in api_url --- src/bot/api_url.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bot/api_url.rs b/src/bot/api_url.rs index 8c33a38f..ed10afc8 100644 --- a/src/bot/api_url.rs +++ b/src/bot/api_url.rs @@ -1,6 +1,7 @@ #[derive(Debug, Clone)] pub(crate) enum ApiUrl { Default, + // FIXME: remove #[allow] when we use this variant #[allow(dead_code)] Custom(reqwest::Url), } From d7d8bc62464302b12bb53d9200c3b79468c5ea9b Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 24 Nov 2020 19:15:46 +0300 Subject: [PATCH 154/755] Add all tba methods to Requester trait --- Cargo.toml | 2 +- src/adaptors/auto_send.rs | 44 +- src/adaptors/cache_me.rs | 32 +- src/adaptors/throttle.rs | 128 +++++- src/bot/api.rs | 910 +++++++++++++++++++++++++++++++++++++- src/local_macros.rs | 627 ++++++++++++++++++++++++++ src/requests/requester.rs | 684 +++++++++++++++++++++++++++- 7 files changed, 2376 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d550d1f5..e9a9a648 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ serde = { version = "1.0.114", features = ["derive"] } serde_json = "1.0.55" serde_with_macros = "1.1.0" uuid = { version = "0.8.1", features = ["v4"] } # for attaching input files - + derive_more = "0.99.9" mime = "0.3.16" thiserror = "1.0.20" diff --git a/src/adaptors/auto_send.rs b/src/adaptors/auto_send.rs index 98e14ad2..f5c71e61 100644 --- a/src/adaptors/auto_send.rs +++ b/src/adaptors/auto_send.rs @@ -8,7 +8,7 @@ use futures::future::FusedFuture; use crate::{ requests::{HasPayload, Output, Request, Requester}, - types::ChatId, + types::*, }; /// Send requests automatically. @@ -62,23 +62,37 @@ impl AutoSend { } } +macro_rules! f { + ($m:ident $this:ident ($($arg:ident : $T:ty),*)) => { + AutoRequest::new($this.inner().$m($($arg),*)) + }; +} + +macro_rules! fty { + ($T:ident) => { + AutoRequest + }; +} + impl Requester for AutoSend { type Err = B::Err; - type GetMe = AutoRequest; - - fn get_me(&self) -> Self::GetMe { - AutoRequest::new(self.bot.get_me()) - } - - type SendMessage = AutoRequest; - - fn send_message(&self, chat_id: C, text: T) -> Self::SendMessage - where - C: Into, - T: Into, - { - AutoRequest::new(self.bot.send_message(chat_id, text)) + requester_forward! { + get_me, send_message, get_updates, set_webhook, delete_webhook, get_webhook_info, + forward_message, send_photo, send_audio, send_document, send_video, + send_animation, send_voice, send_video_note, send_media_group, send_location, + edit_message_live_location, edit_message_live_location_inline, stop_message_live_location, + stop_message_live_location_inline, send_venue, send_contact, send_poll, send_dice, + send_chat_action, get_user_profile_photos, get_file, kick_chat_member, unban_chat_member, + restrict_chat_member, promote_chat_member, set_chat_administrator_custom_title, set_chat_permissions, + export_chat_invite_link, set_chat_photo, delete_chat_photo, set_chat_title, set_chat_description, + pin_chat_message, unpin_chat_message, leave_chat, get_chat, get_chat_administrators, get_chat_members_count, + get_chat_member, set_chat_sticker_set, delete_chat_sticker_set, answer_callback_query, set_my_commands, + get_my_commands, answer_inline_query, edit_message_text, edit_message_text_inline, edit_message_caption, + edit_message_caption_inline, edit_message_media, edit_message_media_inline, edit_message_reply_markup, + edit_message_reply_markup_inline, stop_poll, delete_message, send_sticker, get_sticker_set, upload_sticker_file, + create_new_sticker_set, add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, + set_sticker_set_thumb, send_invoice, answer_shipping_query, answer_pre_checkout_query, set_passport_data_errors => f, fty } } diff --git a/src/adaptors/cache_me.rs b/src/adaptors/cache_me.rs index b43061e4..3f8000fe 100644 --- a/src/adaptors/cache_me.rs +++ b/src/adaptors/cache_me.rs @@ -11,7 +11,7 @@ use once_cell::sync::OnceCell; use crate::{ payloads::GetMe, requests::{HasPayload, Request, Requester}, - types::{ChatId, User}, + types::{ChatId, User, *}, }; /// `get_me` cache. @@ -54,6 +54,18 @@ impl CacheMe { } } +macro_rules! f { + ($m:ident $this:ident ($($arg:ident : $T:ty),*)) => { + $this.inner().$m($($arg),*) + }; +} + +macro_rules! fty { + ($T:ident) => { + B::$T + }; +} + impl Requester for CacheMe where B: Requester, @@ -81,6 +93,24 @@ where { self.bot.send_message(chat_id, text) } + + requester_forward! { + get_updates, set_webhook, delete_webhook, get_webhook_info, + forward_message, send_photo, send_audio, send_document, send_video, + send_animation, send_voice, send_video_note, send_media_group, send_location, + edit_message_live_location, edit_message_live_location_inline, stop_message_live_location, + stop_message_live_location_inline, send_venue, send_contact, send_poll, send_dice, + send_chat_action, get_user_profile_photos, get_file, kick_chat_member, unban_chat_member, + restrict_chat_member, promote_chat_member, set_chat_administrator_custom_title, set_chat_permissions, + export_chat_invite_link, set_chat_photo, delete_chat_photo, set_chat_title, set_chat_description, + pin_chat_message, unpin_chat_message, leave_chat, get_chat, get_chat_administrators, get_chat_members_count, + get_chat_member, set_chat_sticker_set, delete_chat_sticker_set, answer_callback_query, set_my_commands, + get_my_commands, answer_inline_query, edit_message_text, edit_message_text_inline, edit_message_caption, + edit_message_caption_inline, edit_message_media, edit_message_media_inline, edit_message_reply_markup, + edit_message_reply_markup_inline, stop_poll, delete_message, send_sticker, get_sticker_set, upload_sticker_file, + create_new_sticker_set, add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, + set_sticker_set_thumb, send_invoice, answer_shipping_query, answer_pre_checkout_query, set_passport_data_errors => f, fty + } } pub struct CachedMeRequest>(Inner, GetMe); diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index 8a9481d4..2401e04c 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -19,9 +19,8 @@ use vecrem::VecExt; use crate::{ adaptors::throttle::chan_send::{ChanSend, SendTy}, - payloads::SendMessage, requests::{HasPayload, Output, Request, Requester}, - types::ChatId, + types::*, }; // Throttling is quite complicated this comment describes the algorithm of @@ -348,26 +347,113 @@ impl Throttle { } } +macro_rules! f { + ($m:ident $this:ident ($($arg:ident : $T:ty),*)) => { + ThrottlingRequest( + $this.inner().$m($($arg),*), + $this.queue.clone(), + |p| (&p.payload_ref().chat_id).into(), + ) + }; +} + +macro_rules! fty { + ($T:ident) => { + ThrottlingRequest + }; +} + +macro_rules! fid { + ($m:ident $this:ident ($($arg:ident : $T:ty),*)) => { + $this.inner().$m($($arg),*) + }; +} + +macro_rules! ftyid { + ($T:ident) => { + B::$T + }; +} + impl Requester for Throttle where B::SendMessage: Send, + B::ForwardMessage: Send, + B::SendPhoto: Send, + B::SendAudio: Send, + B::SendDocument: Send, + B::SendVideo: Send, + B::SendAnimation: Send, + B::SendVoice: Send, + B::SendVideoNote: Send, + B::SendMediaGroup: Send, + B::SendLocation: Send, + B::SendVenue: Send, + B::SendContact: Send, + B::SendPoll: Send, + B::SendDice: Send, + B::SendSticker: Send, + B::SendInvoice: Send, { type Err = B::Err; - type GetMe = B::GetMe; - - fn get_me(&self) -> Self::GetMe { - self.bot.get_me() + requester_forward! { + send_message, + forward_message, send_photo, send_audio, send_document, send_video, + send_animation, send_voice, send_video_note, send_media_group, send_location, + send_venue, send_contact, send_poll, send_dice, send_sticker, => f, fty } - type SendMessage = ThrottlingRequest; + type SendInvoice = ThrottlingRequest; - fn send_message(&self, chat_id: C, text: T) -> Self::SendMessage + fn send_invoice( + &self, + chat_id: i32, + title: T, + description: D, + payload: Pa, + provider_token: P, + start_parameter: S, + currency: C, + prices: Pri, + ) -> Self::SendInvoice where - C: Into, T: Into, + D: Into, + Pa: Into, + P: Into, + S: Into, + C: Into, + Pri: IntoIterator, { - ThrottlingRequest(self.bot.send_message(chat_id, text), self.queue.clone()) + ThrottlingRequest( + self.inner().send_invoice( + chat_id, + title, + description, + payload, + provider_token, + start_parameter, + currency, + prices, + ), + self.queue.clone(), + |p| Id::Id(p.payload_ref().chat_id as _), + ) + } + + requester_forward! { + get_me, get_updates, set_webhook, delete_webhook, get_webhook_info, edit_message_live_location, edit_message_live_location_inline, stop_message_live_location, + stop_message_live_location_inline, send_chat_action, get_user_profile_photos, get_file, kick_chat_member, unban_chat_member, + restrict_chat_member, promote_chat_member, set_chat_administrator_custom_title, set_chat_permissions, + export_chat_invite_link, set_chat_photo, delete_chat_photo, set_chat_title, set_chat_description, + pin_chat_message, unpin_chat_message, leave_chat, get_chat, get_chat_administrators, get_chat_members_count, + get_chat_member, set_chat_sticker_set, delete_chat_sticker_set, answer_callback_query, set_my_commands, + get_my_commands, answer_inline_query, edit_message_text, edit_message_text_inline, edit_message_caption, + edit_message_caption_inline, edit_message_media, edit_message_media_inline, edit_message_reply_markup, + edit_message_reply_markup_inline, stop_poll, delete_message, get_sticker_set, upload_sticker_file, + create_new_sticker_set, add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, + set_sticker_set_thumb, answer_shipping_query, answer_pre_checkout_query, set_passport_data_errors => fid, ftyid } } @@ -395,17 +481,11 @@ impl From<&ChatId> for Id { } } -pub trait GetChatId { - fn get_chat_id(&self) -> &ChatId; -} - -impl GetChatId for SendMessage { - fn get_chat_id(&self) -> &ChatId { - &self.chat_id - } -} - -pub struct ThrottlingRequest(R, mpsc::Sender<(Id, Sender)>); +pub struct ThrottlingRequest( + R, + mpsc::Sender<(Id, Sender)>, + fn(&R::Payload) -> Id, +); impl HasPayload for ThrottlingRequest { type Payload = R::Payload; @@ -422,7 +502,6 @@ impl HasPayload for ThrottlingRequest { impl Request for ThrottlingRequest where R: Request + Send, - ::Payload: GetChatId, { type Err = R::Err; type Send = ThrottlingSend; @@ -430,13 +509,14 @@ where fn send(self) -> Self::Send { let (tx, rx) = channel(); - let send = self.1.send_t((self.0.payload_ref().get_chat_id().into(), tx)); + let id = self.2(self.payload_ref()); + let send = self.1.send_t((id, tx)); ThrottlingSend(ThrottlingSendInner::Registering { request: self.0, send, wait: rx }) } fn send_ref(&self) -> Self::SendRef { let (tx, rx) = channel(); - let send = self.1.clone().send_t((self.0.payload_ref().get_chat_id().into(), tx)); + let send = self.1.clone().send_t((self.2(self.payload_ref()), tx)); // As we can't move self.0 (request) out, as we do in `send` we are // forced to call `send_ref()`. This may have overhead and/or lead to diff --git a/src/bot/api.rs b/src/bot/api.rs index d831738e..8427ddba 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -9,8 +9,8 @@ use crate::{ EditMessageReplyMarkup, EditMessageText, ExportChatInviteLink, ForwardMessage, GetChat, GetChatAdministrators, GetChatMember, GetChatMembersCount, GetFile, GetGameHighScores, GetMe, GetMyCommands, GetStickerSet, GetUpdates, GetUpdatesNonStrict, GetUserProfilePhotos, - GetWebhookInfo, JsonRequest, KickChatMember, LeaveChat, PinChatMessage, PromoteChatMember, - Requester, RestrictChatMember, SendAnimation, SendAudio, SendChatAction, + GetWebhookInfo, JsonRequest, KickChatMember, LeaveChat, MultipartRequest, PinChatMessage, + PromoteChatMember, Requester, RestrictChatMember, SendAnimation, SendAudio, SendChatAction, SendChatActionKind, SendContact, SendDice, SendDocument, SendGame, SendInvoice, SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendPoll, SendSticker, SendVenue, SendVideo, SendVideoNote, SendVoice, SetChatAdministratorCustomTitle, SetChatDescription, @@ -1707,19 +1707,921 @@ impl Bot { impl Requester for Bot { type Err = crate::errors::RequestError; + type GetUpdates = JsonRequest; + + fn get_updates(&self) -> Self::GetUpdates where { + Self::GetUpdates::new(self.clone(), payloads::GetUpdates::new()) + } + + type SetWebhook = JsonRequest; + + fn set_webhook(&self, url: U, allowed_updates: A) -> Self::SetWebhook + where + U: Into, + A: IntoIterator, + { + Self::SetWebhook::new(self.clone(), payloads::SetWebhook::new(url, allowed_updates)) + } + + type DeleteWebhook = JsonRequest; + + fn delete_webhook(&self) -> Self::DeleteWebhook where { + Self::DeleteWebhook::new(self.clone(), payloads::DeleteWebhook::new()) + } + + type GetWebhookInfo = JsonRequest; + + fn get_webhook_info(&self) -> Self::GetWebhookInfo where { + Self::GetWebhookInfo::new(self.clone(), payloads::GetWebhookInfo::new()) + } + type GetMe = JsonRequest; - fn get_me(&self) -> JsonRequest { + fn get_me(&self) -> Self::GetMe where { Self::GetMe::new(self.clone(), payloads::GetMe::new()) } type SendMessage = JsonRequest; - fn send_message(&self, chat_id: C, text: T) -> JsonRequest + fn send_message(&self, chat_id: C, text: T) -> Self::SendMessage where C: Into, T: Into, { Self::SendMessage::new(self.clone(), payloads::SendMessage::new(chat_id, text)) } + + type ForwardMessage = JsonRequest; + + fn forward_message( + &self, + chat_id: C, + from_chat_id: F, + message_id: i32, + ) -> Self::ForwardMessage + where + C: Into, + F: Into, + { + Self::ForwardMessage::new( + self.clone(), + payloads::ForwardMessage::new(chat_id, from_chat_id, message_id), + ) + } + + type SendPhoto = MultipartRequest; + + fn send_photo(&self, chat_id: Ch, photo: InputFile, caption: Ca) -> Self::SendPhoto + where + Ch: Into, + Ca: Into, + { + Self::SendPhoto::new(self.clone(), payloads::SendPhoto::new(chat_id, photo, caption)) + } + + type SendAudio = MultipartRequest; + + fn send_audio(&self, chat_id: Ch, audio: InputFile, caption: Ca) -> Self::SendAudio + where + Ch: Into, + Ca: Into, + { + Self::SendAudio::new(self.clone(), payloads::SendAudio::new(chat_id, audio, caption)) + } + + type SendDocument = MultipartRequest; + + fn send_document( + &self, + chat_id: Ch, + document: InputFile, + caption: Ca, + ) -> Self::SendDocument + where + Ch: Into, + Ca: Into, + { + Self::SendDocument::new( + self.clone(), + payloads::SendDocument::new(chat_id, document, caption), + ) + } + + type SendVideo = MultipartRequest; + + fn send_video(&self, chat_id: Ch, video: InputFile, caption: Ca) -> Self::SendVideo + where + Ch: Into, + Ca: Into, + { + Self::SendVideo::new(self.clone(), payloads::SendVideo::new(chat_id, video, caption)) + } + + type SendAnimation = MultipartRequest; + + fn send_animation( + &self, + chat_id: Ch, + animation: InputFile, + caption: Ca, + ) -> Self::SendAnimation + where + Ch: Into, + Ca: Into, + { + Self::SendAnimation::new( + self.clone(), + payloads::SendAnimation::new(chat_id, animation, caption), + ) + } + + type SendVoice = MultipartRequest; + + fn send_voice(&self, chat_id: Ch, voice: InputFile, caption: Ca) -> Self::SendVoice + where + Ch: Into, + Ca: Into, + { + Self::SendVoice::new(self.clone(), payloads::SendVoice::new(chat_id, voice, caption)) + } + + type SendVideoNote = MultipartRequest; + + fn send_video_note(&self, chat_id: C, video_note: InputFile) -> Self::SendVideoNote + where + C: Into, + { + Self::SendVideoNote::new(self.clone(), payloads::SendVideoNote::new(chat_id, video_note)) + } + + type SendMediaGroup = MultipartRequest; + + fn send_media_group(&self, chat_id: C, media: M) -> Self::SendMediaGroup + where + C: Into, + M: IntoIterator, + { + Self::SendMediaGroup::new(self.clone(), payloads::SendMediaGroup::new(chat_id, media)) + } + + type SendLocation = JsonRequest; + + fn send_location( + &self, + chat_id: C, + latitude: f64, + longitude: f64, + live_period: u32, + ) -> Self::SendLocation + where + C: Into, + { + Self::SendLocation::new( + self.clone(), + payloads::SendLocation::new(chat_id, latitude, longitude, live_period), + ) + } + + type EditMessageLiveLocation = JsonRequest; + + fn edit_message_live_location( + &self, + chat_id: C, + message_id: i32, + latitude: f64, + longitude: f64, + ) -> Self::EditMessageLiveLocation + where + C: Into, + { + Self::EditMessageLiveLocation::new( + self.clone(), + payloads::EditMessageLiveLocation::new(chat_id, message_id, latitude, longitude), + ) + } + + type EditMessageLiveLocationInline = JsonRequest; + + fn edit_message_live_location_inline( + &self, + inline_message_id: I, + latitude: f64, + longitude: f64, + ) -> Self::EditMessageLiveLocationInline + where + I: Into, + { + Self::EditMessageLiveLocationInline::new( + self.clone(), + payloads::EditMessageLiveLocationInline::new(inline_message_id, latitude, longitude), + ) + } + + type StopMessageLiveLocation = JsonRequest; + + fn stop_message_live_location( + &self, + chat_id: C, + message_id: i32, + latitude: f64, + longitude: f64, + ) -> Self::StopMessageLiveLocation + where + C: Into, + { + Self::StopMessageLiveLocation::new( + self.clone(), + payloads::StopMessageLiveLocation::new(chat_id, message_id, latitude, longitude), + ) + } + + type StopMessageLiveLocationInline = JsonRequest; + + fn stop_message_live_location_inline( + &self, + inline_message_id: I, + latitude: f64, + longitude: f64, + ) -> Self::StopMessageLiveLocationInline + where + I: Into, + { + Self::StopMessageLiveLocationInline::new( + self.clone(), + payloads::StopMessageLiveLocationInline::new(inline_message_id, latitude, longitude), + ) + } + + type SendVenue = JsonRequest; + + fn send_venue( + &self, + chat_id: C, + latitude: f64, + longitude: f64, + title: T, + address: A, + ) -> Self::SendVenue + where + C: Into, + T: Into, + A: Into, + { + Self::SendVenue::new( + self.clone(), + payloads::SendVenue::new(chat_id, latitude, longitude, title, address), + ) + } + + type SendContact = JsonRequest; + + fn send_contact(&self, chat_id: C, phone_number: f64, first_name: f64) -> Self::SendContact + where + C: Into, + { + Self::SendContact::new( + self.clone(), + payloads::SendContact::new(chat_id, phone_number, first_name), + ) + } + + type SendPoll = JsonRequest; + + fn send_poll( + &self, + chat_id: C, + question: Q, + options: O, + type_: crate::types::PollType, + ) -> Self::SendPoll + where + C: Into, + Q: Into, + O: IntoIterator, + { + Self::SendPoll::new( + self.clone(), + payloads::SendPoll::new(chat_id, question, options, type_), + ) + } + + type SendDice = JsonRequest; + + fn send_dice(&self, chat_id: C, emoji: crate::types::DiceEmoji) -> Self::SendDice + where + C: Into, + { + Self::SendDice::new(self.clone(), payloads::SendDice::new(chat_id, emoji)) + } + + type SendChatAction = JsonRequest; + + fn send_chat_action( + &self, + chat_id: C, + action: crate::types::ChatAction, + ) -> Self::SendChatAction + where + C: Into, + { + Self::SendChatAction::new(self.clone(), payloads::SendChatAction::new(chat_id, action)) + } + + type GetUserProfilePhotos = JsonRequest; + + fn get_user_profile_photos(&self, user_id: i32) -> Self::GetUserProfilePhotos where { + Self::GetUserProfilePhotos::new(self.clone(), payloads::GetUserProfilePhotos::new(user_id)) + } + + type GetFile = JsonRequest; + + fn get_file(&self, file_id: F) -> Self::GetFile + where + F: Into, + { + Self::GetFile::new(self.clone(), payloads::GetFile::new(file_id)) + } + + type KickChatMember = JsonRequest; + + fn kick_chat_member(&self, chat_id: C, user_id: i32) -> Self::KickChatMember + where + C: Into, + { + Self::KickChatMember::new(self.clone(), payloads::KickChatMember::new(chat_id, user_id)) + } + + type UnbanChatMember = JsonRequest; + + fn unban_chat_member(&self, chat_id: C, user_id: i32) -> Self::UnbanChatMember + where + C: Into, + { + Self::UnbanChatMember::new(self.clone(), payloads::UnbanChatMember::new(chat_id, user_id)) + } + + type RestrictChatMember = JsonRequest; + + fn restrict_chat_member( + &self, + chat_id: C, + user_id: i32, + permissions: ChatPermissions, + ) -> Self::RestrictChatMember + where + C: Into, + { + Self::RestrictChatMember::new( + self.clone(), + payloads::RestrictChatMember::new(chat_id, user_id, permissions), + ) + } + + type PromoteChatMember = JsonRequest; + + fn promote_chat_member(&self, chat_id: C, user_id: i32) -> Self::PromoteChatMember + where + C: Into, + { + Self::PromoteChatMember::new( + self.clone(), + payloads::PromoteChatMember::new(chat_id, user_id), + ) + } + + type SetChatAdministratorCustomTitle = JsonRequest; + + fn set_chat_administrator_custom_title( + &self, + chat_id: Ch, + user_id: i32, + custom_title: Cu, + ) -> Self::SetChatAdministratorCustomTitle + where + Ch: Into, + Cu: Into, + { + Self::SetChatAdministratorCustomTitle::new( + self.clone(), + payloads::SetChatAdministratorCustomTitle::new(chat_id, user_id, custom_title), + ) + } + + type SetChatPermissions = JsonRequest; + + fn set_chat_permissions( + &self, + chat_id: C, + permissions: ChatPermissions, + ) -> Self::SetChatPermissions + where + C: Into, + { + Self::SetChatPermissions::new( + self.clone(), + payloads::SetChatPermissions::new(chat_id, permissions), + ) + } + + type ExportChatInviteLink = JsonRequest; + + fn export_chat_invite_link(&self, chat_id: C) -> Self::ExportChatInviteLink + where + C: Into, + { + Self::ExportChatInviteLink::new(self.clone(), payloads::ExportChatInviteLink::new(chat_id)) + } + + type SetChatPhoto = MultipartRequest; + + fn set_chat_photo(&self, chat_id: C, photo: InputFile) -> Self::SetChatPhoto + where + C: Into, + { + Self::SetChatPhoto::new(self.clone(), payloads::SetChatPhoto::new(chat_id, photo)) + } + + type DeleteChatPhoto = JsonRequest; + + fn delete_chat_photo(&self, chat_id: C) -> Self::DeleteChatPhoto + where + C: Into, + { + Self::DeleteChatPhoto::new(self.clone(), payloads::DeleteChatPhoto::new(chat_id)) + } + + type SetChatTitle = JsonRequest; + + fn set_chat_title(&self, chat_id: C, title: T) -> Self::SetChatTitle + where + C: Into, + T: Into, + { + Self::SetChatTitle::new(self.clone(), payloads::SetChatTitle::new(chat_id, title)) + } + + type SetChatDescription = JsonRequest; + + fn set_chat_description(&self, chat_id: C) -> Self::SetChatDescription + where + C: Into, + { + Self::SetChatDescription::new(self.clone(), payloads::SetChatDescription::new(chat_id)) + } + + type PinChatMessage = JsonRequest; + + fn pin_chat_message(&self, chat_id: C, message_id: i32) -> Self::PinChatMessage + where + C: Into, + { + Self::PinChatMessage::new(self.clone(), payloads::PinChatMessage::new(chat_id, message_id)) + } + + type UnpinChatMessage = JsonRequest; + + fn unpin_chat_message(&self, chat_id: C) -> Self::UnpinChatMessage + where + C: Into, + { + Self::UnpinChatMessage::new(self.clone(), payloads::UnpinChatMessage::new(chat_id)) + } + + type LeaveChat = JsonRequest; + + fn leave_chat(&self, chat_id: C) -> Self::LeaveChat + where + C: Into, + { + Self::LeaveChat::new(self.clone(), payloads::LeaveChat::new(chat_id)) + } + + type GetChat = JsonRequest; + + fn get_chat(&self, chat_id: C) -> Self::GetChat + where + C: Into, + { + Self::GetChat::new(self.clone(), payloads::GetChat::new(chat_id)) + } + + type GetChatAdministrators = JsonRequest; + + fn get_chat_administrators(&self, chat_id: C) -> Self::GetChatAdministrators + where + C: Into, + { + Self::GetChatAdministrators::new( + self.clone(), + payloads::GetChatAdministrators::new(chat_id), + ) + } + + type GetChatMembersCount = JsonRequest; + + fn get_chat_members_count(&self, chat_id: C) -> Self::GetChatMembersCount + where + C: Into, + { + Self::GetChatMembersCount::new(self.clone(), payloads::GetChatMembersCount::new(chat_id)) + } + + type GetChatMember = JsonRequest; + + fn get_chat_member(&self, chat_id: C, user_id: i32) -> Self::GetChatMember + where + C: Into, + { + Self::GetChatMember::new(self.clone(), payloads::GetChatMember::new(chat_id, user_id)) + } + + type SetChatStickerSet = JsonRequest; + + fn set_chat_sticker_set(&self, chat_id: C, sticker_set_name: S) -> Self::SetChatStickerSet + where + C: Into, + S: Into, + { + Self::SetChatStickerSet::new( + self.clone(), + payloads::SetChatStickerSet::new(chat_id, sticker_set_name), + ) + } + + type DeleteChatStickerSet = JsonRequest; + + fn delete_chat_sticker_set(&self, chat_id: C) -> Self::DeleteChatStickerSet + where + C: Into, + { + Self::DeleteChatStickerSet::new(self.clone(), payloads::DeleteChatStickerSet::new(chat_id)) + } + + type AnswerCallbackQuery = JsonRequest; + + fn answer_callback_query(&self, callback_query_id: C) -> Self::AnswerCallbackQuery + where + C: Into, + { + Self::AnswerCallbackQuery::new( + self.clone(), + payloads::AnswerCallbackQuery::new(callback_query_id), + ) + } + + type SetMyCommands = JsonRequest; + + fn set_my_commands(&self, commands: C) -> Self::SetMyCommands + where + C: IntoIterator, + { + Self::SetMyCommands::new(self.clone(), payloads::SetMyCommands::new(commands)) + } + + type GetMyCommands = JsonRequest; + + fn get_my_commands(&self) -> Self::GetMyCommands where { + Self::GetMyCommands::new(self.clone(), payloads::GetMyCommands::new()) + } + + type AnswerInlineQuery = JsonRequest; + + fn answer_inline_query(&self, inline_query_id: I, results: R) -> Self::AnswerInlineQuery + where + I: Into, + R: IntoIterator, + { + Self::AnswerInlineQuery::new( + self.clone(), + payloads::AnswerInlineQuery::new(inline_query_id, results), + ) + } + + type EditMessageText = JsonRequest; + + fn edit_message_text(&self, chat_id: C, message_id: i32, text: T) -> Self::EditMessageText + where + C: Into, + T: Into, + { + Self::EditMessageText::new( + self.clone(), + payloads::EditMessageText::new(chat_id, message_id, text), + ) + } + + type EditMessageTextInline = JsonRequest; + + fn edit_message_text_inline( + &self, + inline_message_id: I, + text: T, + ) -> Self::EditMessageTextInline + where + I: Into, + T: Into, + { + Self::EditMessageTextInline::new( + self.clone(), + payloads::EditMessageTextInline::new(inline_message_id, text), + ) + } + + type EditMessageCaption = JsonRequest; + + fn edit_message_caption( + &self, + chat_id: Ch, + message_id: i32, + caption: Ca, + ) -> Self::EditMessageCaption + where + Ch: Into, + Ca: Into, + { + Self::EditMessageCaption::new( + self.clone(), + payloads::EditMessageCaption::new(chat_id, message_id, caption), + ) + } + + type EditMessageCaptionInline = JsonRequest; + + fn edit_message_caption_inline( + &self, + inline_message_id: I, + caption: C, + ) -> Self::EditMessageCaptionInline + where + I: Into, + C: Into, + { + Self::EditMessageCaptionInline::new( + self.clone(), + payloads::EditMessageCaptionInline::new(inline_message_id, caption), + ) + } + + type EditMessageMedia = MultipartRequest; + + fn edit_message_media( + &self, + chat_id: C, + message_id: i32, + media: InputMedia, + ) -> Self::EditMessageMedia + where + C: Into, + { + Self::EditMessageMedia::new( + self.clone(), + payloads::EditMessageMedia::new(chat_id, message_id, media), + ) + } + + type EditMessageMediaInline = MultipartRequest; + + fn edit_message_media_inline( + &self, + inline_message_id: I, + media: InputMedia, + ) -> Self::EditMessageMediaInline + where + I: Into, + { + Self::EditMessageMediaInline::new( + self.clone(), + payloads::EditMessageMediaInline::new(inline_message_id, media), + ) + } + + type EditMessageReplyMarkup = JsonRequest; + + fn edit_message_reply_markup( + &self, + chat_id: C, + message_id: i32, + ) -> Self::EditMessageReplyMarkup + where + C: Into, + { + Self::EditMessageReplyMarkup::new( + self.clone(), + payloads::EditMessageReplyMarkup::new(chat_id, message_id), + ) + } + + type EditMessageReplyMarkupInline = JsonRequest; + + fn edit_message_reply_markup_inline( + &self, + inline_message_id: I, + ) -> Self::EditMessageReplyMarkupInline + where + I: Into, + { + Self::EditMessageReplyMarkupInline::new( + self.clone(), + payloads::EditMessageReplyMarkupInline::new(inline_message_id), + ) + } + + type StopPoll = JsonRequest; + + fn stop_poll(&self, chat_id: C, message_id: i32) -> Self::StopPoll + where + C: Into, + { + Self::StopPoll::new(self.clone(), payloads::StopPoll::new(chat_id, message_id)) + } + + type DeleteMessage = JsonRequest; + + fn delete_message(&self, chat_id: C, message_id: i32) -> Self::DeleteMessage + where + C: Into, + { + Self::DeleteMessage::new(self.clone(), payloads::DeleteMessage::new(chat_id, message_id)) + } + + type SendSticker = MultipartRequest; + + fn send_sticker(&self, chat_id: C, sticker: InputFile) -> Self::SendSticker + where + C: Into, + { + Self::SendSticker::new(self.clone(), payloads::SendSticker::new(chat_id, sticker)) + } + + type GetStickerSet = JsonRequest; + + fn get_sticker_set(&self, name: N) -> Self::GetStickerSet + where + N: Into, + { + Self::GetStickerSet::new(self.clone(), payloads::GetStickerSet::new(name)) + } + + type UploadStickerFile = MultipartRequest; + + fn upload_sticker_file(&self, user_id: i32, png_sticker: InputFile) -> Self::UploadStickerFile where + { + Self::UploadStickerFile::new( + self.clone(), + payloads::UploadStickerFile::new(user_id, png_sticker), + ) + } + + type CreateNewStickerSet = JsonRequest; + + fn create_new_sticker_set( + &self, + user_id: i32, + name: N, + title: T, + emojis: E, + ) -> Self::CreateNewStickerSet + where + N: Into, + T: Into, + E: Into, + { + Self::CreateNewStickerSet::new( + self.clone(), + payloads::CreateNewStickerSet::new(user_id, name, title, emojis), + ) + } + + type AddStickerToSet = MultipartRequest; + + fn add_sticker_to_set( + &self, + user_id: i32, + name: N, + sticker: InputSticker, + emojis: E, + ) -> Self::AddStickerToSet + where + N: Into, + E: Into, + { + Self::AddStickerToSet::new( + self.clone(), + payloads::AddStickerToSet::new(user_id, name, sticker, emojis), + ) + } + + type SetStickerPositionInSet = JsonRequest; + + fn set_sticker_position_in_set( + &self, + sticker: S, + position: u32, + ) -> Self::SetStickerPositionInSet + where + S: Into, + { + Self::SetStickerPositionInSet::new( + self.clone(), + payloads::SetStickerPositionInSet::new(sticker, position), + ) + } + + type DeleteStickerFromSet = JsonRequest; + + fn delete_sticker_from_set(&self, sticker: S) -> Self::DeleteStickerFromSet + where + S: Into, + { + Self::DeleteStickerFromSet::new(self.clone(), payloads::DeleteStickerFromSet::new(sticker)) + } + + type SetStickerSetThumb = MultipartRequest; + + fn set_sticker_set_thumb(&self, name: N, user_id: i32) -> Self::SetStickerSetThumb + where + N: Into, + { + Self::SetStickerSetThumb::new( + self.clone(), + payloads::SetStickerSetThumb::new(name, user_id), + ) + } + + type SendInvoice = JsonRequest; + + fn send_invoice( + &self, + chat_id: i32, + title: T, + description: D, + payload: Pa, + provider_token: P, + start_parameter: S, + currency: C, + prices: Pri, + ) -> Self::SendInvoice + where + T: Into, + D: Into, + Pa: Into, + P: Into, + S: Into, + C: Into, + Pri: IntoIterator, + { + Self::SendInvoice::new( + self.clone(), + payloads::SendInvoice::new( + chat_id, + title, + description, + payload, + provider_token, + start_parameter, + currency, + prices, + ), + ) + } + + type AnswerShippingQuery = JsonRequest; + + fn answer_shipping_query(&self, shipping_query_id: S, ok: bool) -> Self::AnswerShippingQuery + where + S: Into, + { + Self::AnswerShippingQuery::new( + self.clone(), + payloads::AnswerShippingQuery::new(shipping_query_id, ok), + ) + } + + type AnswerPreCheckoutQuery = JsonRequest; + + fn answer_pre_checkout_query

( + &self, + pre_checkout_query_id: P, + ok: bool, + ) -> Self::AnswerPreCheckoutQuery + where + P: Into, + { + Self::AnswerPreCheckoutQuery::new( + self.clone(), + payloads::AnswerPreCheckoutQuery::new(pre_checkout_query_id, ok), + ) + } + + type SetPassportDataErrors = JsonRequest; + + fn set_passport_data_errors(&self, user_id: i32, errors: E) -> Self::SetPassportDataErrors + where + E: IntoIterator, + { + Self::SetPassportDataErrors::new( + self.clone(), + payloads::SetPassportDataErrors::new(user_id, errors), + ) + } } diff --git a/src/local_macros.rs b/src/local_macros.rs index 971b2fa5..fc522950 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -365,3 +365,630 @@ macro_rules! impl_payload { $e }; } + +#[macro_use] +// This macro is auto generated by `cg` (fea4d31). +// **DO NOT EDIT THIS MACRO**, +// edit `cg` instead. +macro_rules! requester_forward { + ($i:ident $(, $rest:ident )* $(,)? => $body:ident, $ty:ident ) => { + requester_forward!(@method $i $body $ty); + $( + requester_forward!(@method $rest $body $ty); + )* + }; + (@method get_updates $body:ident $ty:ident) => { + type GetUpdates = $ty![GetUpdates]; + + fn get_updates<>(&self, ) -> Self::GetUpdates where { + let this = self; + $body!(get_updates this ()) + } + }; + (@method set_webhook $body:ident $ty:ident) => { + type SetWebhook = $ty![SetWebhook]; + + fn set_webhook(&self, url: U, allowed_updates: A) -> Self::SetWebhook where U: Into, + A: IntoIterator { + let this = self; + $body!(set_webhook this (url: U, allowed_updates: A)) + } + }; + (@method delete_webhook $body:ident $ty:ident) => { + type DeleteWebhook = $ty![DeleteWebhook]; + + fn delete_webhook<>(&self, ) -> Self::DeleteWebhook where { + let this = self; + $body!(delete_webhook this ()) + } + }; + (@method get_webhook_info $body:ident $ty:ident) => { + type GetWebhookInfo = $ty![GetWebhookInfo]; + + fn get_webhook_info<>(&self, ) -> Self::GetWebhookInfo where { + let this = self; + $body!(get_webhook_info this ()) + } + }; + (@method get_me $body:ident $ty:ident) => { + type GetMe = $ty![GetMe]; + + fn get_me<>(&self, ) -> Self::GetMe where { + let this = self; + $body!(get_me this ()) + } + }; + (@method send_message $body:ident $ty:ident) => { + type SendMessage = $ty![SendMessage]; + + fn send_message(&self, chat_id: C, text: T) -> Self::SendMessage where C: Into, + T: Into { + let this = self; + $body!(send_message this (chat_id: C, text: T)) + } + }; + (@method forward_message $body:ident $ty:ident) => { + type ForwardMessage = $ty![ForwardMessage]; + + fn forward_message(&self, chat_id: C, from_chat_id: F, message_id: i32) -> Self::ForwardMessage where C: Into, + F: Into { + let this = self; + $body!(forward_message this (chat_id: C, from_chat_id: F, message_id: i32)) + } + }; + (@method send_photo $body:ident $ty:ident) => { + type SendPhoto = $ty![SendPhoto]; + + fn send_photo(&self, chat_id: Ch, photo: InputFile, caption: Ca) -> Self::SendPhoto where Ch: Into, + Ca: Into { + let this = self; + $body!(send_photo this (chat_id: Ch, photo: InputFile, caption: Ca)) + } + }; + (@method send_audio $body:ident $ty:ident) => { + type SendAudio = $ty![SendAudio]; + + fn send_audio(&self, chat_id: Ch, audio: InputFile, caption: Ca) -> Self::SendAudio where Ch: Into, + Ca: Into { + let this = self; + $body!(send_audio this (chat_id: Ch, audio: InputFile, caption: Ca)) + } + }; + (@method send_document $body:ident $ty:ident) => { + type SendDocument = $ty![SendDocument]; + + fn send_document(&self, chat_id: Ch, document: InputFile, caption: Ca) -> Self::SendDocument where Ch: Into, + Ca: Into { + let this = self; + $body!(send_document this (chat_id: Ch, document: InputFile, caption: Ca)) + } + }; + (@method send_video $body:ident $ty:ident) => { + type SendVideo = $ty![SendVideo]; + + fn send_video(&self, chat_id: Ch, video: InputFile, caption: Ca) -> Self::SendVideo where Ch: Into, + Ca: Into { + let this = self; + $body!(send_video this (chat_id: Ch, video: InputFile, caption: Ca)) + } + }; + (@method send_animation $body:ident $ty:ident) => { + type SendAnimation = $ty![SendAnimation]; + + fn send_animation(&self, chat_id: Ch, animation: InputFile, caption: Ca) -> Self::SendAnimation where Ch: Into, + Ca: Into { + let this = self; + $body!(send_animation this (chat_id: Ch, animation: InputFile, caption: Ca)) + } + }; + (@method send_voice $body:ident $ty:ident) => { + type SendVoice = $ty![SendVoice]; + + fn send_voice(&self, chat_id: Ch, voice: InputFile, caption: Ca) -> Self::SendVoice where Ch: Into, + Ca: Into { + let this = self; + $body!(send_voice this (chat_id: Ch, voice: InputFile, caption: Ca)) + } + }; + (@method send_video_note $body:ident $ty:ident) => { + type SendVideoNote = $ty![SendVideoNote]; + + fn send_video_note(&self, chat_id: C, video_note: InputFile) -> Self::SendVideoNote where C: Into { + let this = self; + $body!(send_video_note this (chat_id: C, video_note: InputFile)) + } + }; + (@method send_media_group $body:ident $ty:ident) => { + type SendMediaGroup = $ty![SendMediaGroup]; + + fn send_media_group(&self, chat_id: C, media: M) -> Self::SendMediaGroup where C: Into, + M: IntoIterator { + let this = self; + $body!(send_media_group this (chat_id: C, media: M)) + } + }; + (@method send_location $body:ident $ty:ident) => { + type SendLocation = $ty![SendLocation]; + + fn send_location(&self, chat_id: C, latitude: f64, longitude: f64, live_period: u32) -> Self::SendLocation where C: Into { + let this = self; + $body!(send_location this (chat_id: C, latitude: f64, longitude: f64, live_period: u32)) + } + }; + (@method edit_message_live_location $body:ident $ty:ident) => { + type EditMessageLiveLocation = $ty![EditMessageLiveLocation]; + + fn edit_message_live_location(&self, chat_id: C, message_id: i32, latitude: f64, longitude: f64) -> Self::EditMessageLiveLocation where C: Into { + let this = self; + $body!(edit_message_live_location this (chat_id: C, message_id: i32, latitude: f64, longitude: f64)) + } + }; + (@method edit_message_live_location_inline $body:ident $ty:ident) => { + type EditMessageLiveLocationInline = $ty![EditMessageLiveLocationInline]; + + fn edit_message_live_location_inline(&self, inline_message_id: I, latitude: f64, longitude: f64) -> Self::EditMessageLiveLocationInline where I: Into { + let this = self; + $body!(edit_message_live_location_inline this (inline_message_id: I, latitude: f64, longitude: f64)) + } + }; + (@method stop_message_live_location $body:ident $ty:ident) => { + type StopMessageLiveLocation = $ty![StopMessageLiveLocation]; + + fn stop_message_live_location(&self, chat_id: C, message_id: i32, latitude: f64, longitude: f64) -> Self::StopMessageLiveLocation where C: Into { + let this = self; + $body!(stop_message_live_location this (chat_id: C, message_id: i32, latitude: f64, longitude: f64)) + } + }; + (@method stop_message_live_location_inline $body:ident $ty:ident) => { + type StopMessageLiveLocationInline = $ty![StopMessageLiveLocationInline]; + + fn stop_message_live_location_inline(&self, inline_message_id: I, latitude: f64, longitude: f64) -> Self::StopMessageLiveLocationInline where I: Into { + let this = self; + $body!(stop_message_live_location_inline this (inline_message_id: I, latitude: f64, longitude: f64)) + } + }; + (@method send_venue $body:ident $ty:ident) => { + type SendVenue = $ty![SendVenue]; + + fn send_venue(&self, chat_id: C, latitude: f64, longitude: f64, title: T, address: A) -> Self::SendVenue where C: Into, + T: Into, + A: Into { + let this = self; + $body!(send_venue this (chat_id: C, latitude: f64, longitude: f64, title: T, address: A)) + } + }; + (@method send_contact $body:ident $ty:ident) => { + type SendContact = $ty![SendContact]; + + fn send_contact(&self, chat_id: C, phone_number: f64, first_name: f64) -> Self::SendContact where C: Into { + let this = self; + $body!(send_contact this (chat_id: C, phone_number: f64, first_name: f64)) + } + }; + (@method send_poll $body:ident $ty:ident) => { + type SendPoll = $ty![SendPoll]; + + fn send_poll(&self, chat_id: C, question: Q, options: O, type_: PollType) -> Self::SendPoll where C: Into, + Q: Into, + O: IntoIterator { + let this = self; + $body!(send_poll this (chat_id: C, question: Q, options: O, type_: PollType)) + } + }; + (@method send_dice $body:ident $ty:ident) => { + type SendDice = $ty![SendDice]; + + fn send_dice(&self, chat_id: C, emoji: DiceEmoji) -> Self::SendDice where C: Into { + let this = self; + $body!(send_dice this (chat_id: C, emoji: DiceEmoji)) + } + }; + (@method send_chat_action $body:ident $ty:ident) => { + type SendChatAction = $ty![SendChatAction]; + + fn send_chat_action(&self, chat_id: C, action: ChatAction) -> Self::SendChatAction where C: Into { + let this = self; + $body!(send_chat_action this (chat_id: C, action: ChatAction)) + } + }; + (@method get_user_profile_photos $body:ident $ty:ident) => { + type GetUserProfilePhotos = $ty![GetUserProfilePhotos]; + + fn get_user_profile_photos<>(&self, user_id: i32) -> Self::GetUserProfilePhotos where { + let this = self; + $body!(get_user_profile_photos this (user_id: i32)) + } + }; + (@method get_file $body:ident $ty:ident) => { + type GetFile = $ty![GetFile]; + + fn get_file(&self, file_id: F) -> Self::GetFile where F: Into { + let this = self; + $body!(get_file this (file_id: F)) + } + }; + (@method kick_chat_member $body:ident $ty:ident) => { + type KickChatMember = $ty![KickChatMember]; + + fn kick_chat_member(&self, chat_id: C, user_id: i32) -> Self::KickChatMember where C: Into { + let this = self; + $body!(kick_chat_member this (chat_id: C, user_id: i32)) + } + }; + (@method unban_chat_member $body:ident $ty:ident) => { + type UnbanChatMember = $ty![UnbanChatMember]; + + fn unban_chat_member(&self, chat_id: C, user_id: i32) -> Self::UnbanChatMember where C: Into { + let this = self; + $body!(unban_chat_member this (chat_id: C, user_id: i32)) + } + }; + (@method restrict_chat_member $body:ident $ty:ident) => { + type RestrictChatMember = $ty![RestrictChatMember]; + + fn restrict_chat_member(&self, chat_id: C, user_id: i32, permissions: ChatPermissions) -> Self::RestrictChatMember where C: Into { + let this = self; + $body!(restrict_chat_member this (chat_id: C, user_id: i32, permissions: ChatPermissions)) + } + }; + (@method promote_chat_member $body:ident $ty:ident) => { + type PromoteChatMember = $ty![PromoteChatMember]; + + fn promote_chat_member(&self, chat_id: C, user_id: i32) -> Self::PromoteChatMember where C: Into { + let this = self; + $body!(promote_chat_member this (chat_id: C, user_id: i32)) + } + }; + (@method set_chat_administrator_custom_title $body:ident $ty:ident) => { + type SetChatAdministratorCustomTitle = $ty![SetChatAdministratorCustomTitle]; + + fn set_chat_administrator_custom_title(&self, chat_id: Ch, user_id: i32, custom_title: Cu) -> Self::SetChatAdministratorCustomTitle where Ch: Into, + Cu: Into { + let this = self; + $body!(set_chat_administrator_custom_title this (chat_id: Ch, user_id: i32, custom_title: Cu)) + } + }; + (@method set_chat_permissions $body:ident $ty:ident) => { + type SetChatPermissions = $ty![SetChatPermissions]; + + fn set_chat_permissions(&self, chat_id: C, permissions: ChatPermissions) -> Self::SetChatPermissions where C: Into { + let this = self; + $body!(set_chat_permissions this (chat_id: C, permissions: ChatPermissions)) + } + }; + (@method export_chat_invite_link $body:ident $ty:ident) => { + type ExportChatInviteLink = $ty![ExportChatInviteLink]; + + fn export_chat_invite_link(&self, chat_id: C) -> Self::ExportChatInviteLink where C: Into { + let this = self; + $body!(export_chat_invite_link this (chat_id: C)) + } + }; + (@method set_chat_photo $body:ident $ty:ident) => { + type SetChatPhoto = $ty![SetChatPhoto]; + + fn set_chat_photo(&self, chat_id: C, photo: InputFile) -> Self::SetChatPhoto where C: Into { + let this = self; + $body!(set_chat_photo this (chat_id: C, photo: InputFile)) + } + }; + (@method delete_chat_photo $body:ident $ty:ident) => { + type DeleteChatPhoto = $ty![DeleteChatPhoto]; + + fn delete_chat_photo(&self, chat_id: C) -> Self::DeleteChatPhoto where C: Into { + let this = self; + $body!(delete_chat_photo this (chat_id: C)) + } + }; + (@method set_chat_title $body:ident $ty:ident) => { + type SetChatTitle = $ty![SetChatTitle]; + + fn set_chat_title(&self, chat_id: C, title: T) -> Self::SetChatTitle where C: Into, + T: Into { + let this = self; + $body!(set_chat_title this (chat_id: C, title: T)) + } + }; + (@method set_chat_description $body:ident $ty:ident) => { + type SetChatDescription = $ty![SetChatDescription]; + + fn set_chat_description(&self, chat_id: C) -> Self::SetChatDescription where C: Into { + let this = self; + $body!(set_chat_description this (chat_id: C)) + } + }; + (@method pin_chat_message $body:ident $ty:ident) => { + type PinChatMessage = $ty![PinChatMessage]; + + fn pin_chat_message(&self, chat_id: C, message_id: i32) -> Self::PinChatMessage where C: Into { + let this = self; + $body!(pin_chat_message this (chat_id: C, message_id: i32)) + } + }; + (@method unpin_chat_message $body:ident $ty:ident) => { + type UnpinChatMessage = $ty![UnpinChatMessage]; + + fn unpin_chat_message(&self, chat_id: C) -> Self::UnpinChatMessage where C: Into { + let this = self; + $body!(unpin_chat_message this (chat_id: C)) + } + }; + (@method leave_chat $body:ident $ty:ident) => { + type LeaveChat = $ty![LeaveChat]; + + fn leave_chat(&self, chat_id: C) -> Self::LeaveChat where C: Into { + let this = self; + $body!(leave_chat this (chat_id: C)) + } + }; + (@method get_chat $body:ident $ty:ident) => { + type GetChat = $ty![GetChat]; + + fn get_chat(&self, chat_id: C) -> Self::GetChat where C: Into { + let this = self; + $body!(get_chat this (chat_id: C)) + } + }; + (@method get_chat_administrators $body:ident $ty:ident) => { + type GetChatAdministrators = $ty![GetChatAdministrators]; + + fn get_chat_administrators(&self, chat_id: C) -> Self::GetChatAdministrators where C: Into { + let this = self; + $body!(get_chat_administrators this (chat_id: C)) + } + }; + (@method get_chat_members_count $body:ident $ty:ident) => { + type GetChatMembersCount = $ty![GetChatMembersCount]; + + fn get_chat_members_count(&self, chat_id: C) -> Self::GetChatMembersCount where C: Into { + let this = self; + $body!(get_chat_members_count this (chat_id: C)) + } + }; + (@method get_chat_member $body:ident $ty:ident) => { + type GetChatMember = $ty![GetChatMember]; + + fn get_chat_member(&self, chat_id: C, user_id: i32) -> Self::GetChatMember where C: Into { + let this = self; + $body!(get_chat_member this (chat_id: C, user_id: i32)) + } + }; + (@method set_chat_sticker_set $body:ident $ty:ident) => { + type SetChatStickerSet = $ty![SetChatStickerSet]; + + fn set_chat_sticker_set(&self, chat_id: C, sticker_set_name: S) -> Self::SetChatStickerSet where C: Into, + S: Into { + let this = self; + $body!(set_chat_sticker_set this (chat_id: C, sticker_set_name: S)) + } + }; + (@method delete_chat_sticker_set $body:ident $ty:ident) => { + type DeleteChatStickerSet = $ty![DeleteChatStickerSet]; + + fn delete_chat_sticker_set(&self, chat_id: C) -> Self::DeleteChatStickerSet where C: Into { + let this = self; + $body!(delete_chat_sticker_set this (chat_id: C)) + } + }; + (@method answer_callback_query $body:ident $ty:ident) => { + type AnswerCallbackQuery = $ty![AnswerCallbackQuery]; + + fn answer_callback_query(&self, callback_query_id: C) -> Self::AnswerCallbackQuery where C: Into { + let this = self; + $body!(answer_callback_query this (callback_query_id: C)) + } + }; + (@method set_my_commands $body:ident $ty:ident) => { + type SetMyCommands = $ty![SetMyCommands]; + + fn set_my_commands(&self, commands: C) -> Self::SetMyCommands where C: IntoIterator { + let this = self; + $body!(set_my_commands this (commands: C)) + } + }; + (@method get_my_commands $body:ident $ty:ident) => { + type GetMyCommands = $ty![GetMyCommands]; + + fn get_my_commands<>(&self, ) -> Self::GetMyCommands where { + let this = self; + $body!(get_my_commands this ()) + } + }; + (@method answer_inline_query $body:ident $ty:ident) => { + type AnswerInlineQuery = $ty![AnswerInlineQuery]; + + fn answer_inline_query(&self, inline_query_id: I, results: R) -> Self::AnswerInlineQuery where I: Into, + R: IntoIterator { + let this = self; + $body!(answer_inline_query this (inline_query_id: I, results: R)) + } + }; + (@method edit_message_text $body:ident $ty:ident) => { + type EditMessageText = $ty![EditMessageText]; + + fn edit_message_text(&self, chat_id: C, message_id: i32, text: T) -> Self::EditMessageText where C: Into, + T: Into { + let this = self; + $body!(edit_message_text this (chat_id: C, message_id: i32, text: T)) + } + }; + (@method edit_message_text_inline $body:ident $ty:ident) => { + type EditMessageTextInline = $ty![EditMessageTextInline]; + + fn edit_message_text_inline(&self, inline_message_id: I, text: T) -> Self::EditMessageTextInline where I: Into, + T: Into { + let this = self; + $body!(edit_message_text_inline this (inline_message_id: I, text: T)) + } + }; + (@method edit_message_caption $body:ident $ty:ident) => { + type EditMessageCaption = $ty![EditMessageCaption]; + + fn edit_message_caption(&self, chat_id: Ch, message_id: i32, caption: Ca) -> Self::EditMessageCaption where Ch: Into, + Ca: Into { + let this = self; + $body!(edit_message_caption this (chat_id: Ch, message_id: i32, caption: Ca)) + } + }; + (@method edit_message_caption_inline $body:ident $ty:ident) => { + type EditMessageCaptionInline = $ty![EditMessageCaptionInline]; + + fn edit_message_caption_inline(&self, inline_message_id: I, caption: C) -> Self::EditMessageCaptionInline where I: Into, + C: Into { + let this = self; + $body!(edit_message_caption_inline this (inline_message_id: I, caption: C)) + } + }; + (@method edit_message_media $body:ident $ty:ident) => { + type EditMessageMedia = $ty![EditMessageMedia]; + + fn edit_message_media(&self, chat_id: C, message_id: i32, media: InputMedia) -> Self::EditMessageMedia where C: Into { + let this = self; + $body!(edit_message_media this (chat_id: C, message_id: i32, media: InputMedia)) + } + }; + (@method edit_message_media_inline $body:ident $ty:ident) => { + type EditMessageMediaInline = $ty![EditMessageMediaInline]; + + fn edit_message_media_inline(&self, inline_message_id: I, media: InputMedia) -> Self::EditMessageMediaInline where I: Into { + let this = self; + $body!(edit_message_media_inline this (inline_message_id: I, media: InputMedia)) + } + }; + (@method edit_message_reply_markup $body:ident $ty:ident) => { + type EditMessageReplyMarkup = $ty![EditMessageReplyMarkup]; + + fn edit_message_reply_markup(&self, chat_id: C, message_id: i32) -> Self::EditMessageReplyMarkup where C: Into { + let this = self; + $body!(edit_message_reply_markup this (chat_id: C, message_id: i32)) + } + }; + (@method edit_message_reply_markup_inline $body:ident $ty:ident) => { + type EditMessageReplyMarkupInline = $ty![EditMessageReplyMarkupInline]; + + fn edit_message_reply_markup_inline(&self, inline_message_id: I) -> Self::EditMessageReplyMarkupInline where I: Into { + let this = self; + $body!(edit_message_reply_markup_inline this (inline_message_id: I)) + } + }; + (@method stop_poll $body:ident $ty:ident) => { + type StopPoll = $ty![StopPoll]; + + fn stop_poll(&self, chat_id: C, message_id: i32) -> Self::StopPoll where C: Into { + let this = self; + $body!(stop_poll this (chat_id: C, message_id: i32)) + } + }; + (@method delete_message $body:ident $ty:ident) => { + type DeleteMessage = $ty![DeleteMessage]; + + fn delete_message(&self, chat_id: C, message_id: i32) -> Self::DeleteMessage where C: Into { + let this = self; + $body!(delete_message this (chat_id: C, message_id: i32)) + } + }; + (@method send_sticker $body:ident $ty:ident) => { + type SendSticker = $ty![SendSticker]; + + fn send_sticker(&self, chat_id: C, sticker: InputFile) -> Self::SendSticker where C: Into { + let this = self; + $body!(send_sticker this (chat_id: C, sticker: InputFile)) + } + }; + (@method get_sticker_set $body:ident $ty:ident) => { + type GetStickerSet = $ty![GetStickerSet]; + + fn get_sticker_set(&self, name: N) -> Self::GetStickerSet where N: Into { + let this = self; + $body!(get_sticker_set this (name: N)) + } + }; + (@method upload_sticker_file $body:ident $ty:ident) => { + type UploadStickerFile = $ty![UploadStickerFile]; + + fn upload_sticker_file<>(&self, user_id: i32, png_sticker: InputFile) -> Self::UploadStickerFile where { + let this = self; + $body!(upload_sticker_file this (user_id: i32, png_sticker: InputFile)) + } + }; + (@method create_new_sticker_set $body:ident $ty:ident) => { + type CreateNewStickerSet = $ty![CreateNewStickerSet]; + + fn create_new_sticker_set(&self, user_id: i32, name: N, title: T, emojis: E) -> Self::CreateNewStickerSet where N: Into, + T: Into, + E: Into { + let this = self; + $body!(create_new_sticker_set this (user_id: i32, name: N, title: T, emojis: E)) + } + }; + (@method add_sticker_to_set $body:ident $ty:ident) => { + type AddStickerToSet = $ty![AddStickerToSet]; + + fn add_sticker_to_set(&self, user_id: i32, name: N, sticker: InputSticker, emojis: E) -> Self::AddStickerToSet where N: Into, + E: Into { + let this = self; + $body!(add_sticker_to_set this (user_id: i32, name: N, sticker: InputSticker, emojis: E)) + } + }; + (@method set_sticker_position_in_set $body:ident $ty:ident) => { + type SetStickerPositionInSet = $ty![SetStickerPositionInSet]; + + fn set_sticker_position_in_set(&self, sticker: S, position: u32) -> Self::SetStickerPositionInSet where S: Into { + let this = self; + $body!(set_sticker_position_in_set this (sticker: S, position: u32)) + } + };(@method delete_sticker_from_set $body:ident $ty:ident) => { + type DeleteStickerFromSet = $ty![DeleteStickerFromSet]; + + fn delete_sticker_from_set(&self, sticker: S) -> Self::DeleteStickerFromSet where S: Into { + let this = self; + $body!(delete_sticker_from_set this (sticker: S)) + } + }; + (@method set_sticker_set_thumb $body:ident $ty:ident) => { + type SetStickerSetThumb = $ty![SetStickerSetThumb]; + + fn set_sticker_set_thumb(&self, name: N, user_id: i32) -> Self::SetStickerSetThumb where N: Into { + let this = self; + $body!(set_sticker_set_thumb this (name: N, user_id: i32)) + } + }; + (@method send_invoice $body:ident $ty:ident) => { + type SendInvoice = $ty![SendInvoice]; + + fn send_invoice(&self, chat_id: i32, title: T, description: D, payload: Pa, provider_token: P, start_parameter: S, currency: C, prices: Pri) -> Self::SendInvoice where T: Into, + D: Into, + Pa: Into, + P: Into, + S: Into, + C: Into, + Pri: IntoIterator { + let this = self; + $body!(send_invoice this (chat_id: i32, title: T, description: D, payload: Pa, provider_token: P, start_parameter: S, currency: C, prices: Pri)) + } + }; + (@method answer_shipping_query $body:ident $ty:ident) => { + type AnswerShippingQuery = $ty![AnswerShippingQuery]; + + fn answer_shipping_query(&self, shipping_query_id: S, ok: bool) -> Self::AnswerShippingQuery where S: Into { + let this = self; + $body!(answer_shipping_query this (shipping_query_id: S, ok: bool)) + } + }; + (@method answer_pre_checkout_query $body:ident $ty:ident) => { + type AnswerPreCheckoutQuery = $ty![AnswerPreCheckoutQuery]; + + fn answer_pre_checkout_query

(&self, pre_checkout_query_id: P, ok: bool) -> Self::AnswerPreCheckoutQuery where P: Into { + let this = self; + $body!(answer_pre_checkout_query this (pre_checkout_query_id: P, ok: bool)) + } + }; + (@method set_passport_data_errors $body:ident $ty:ident) => { + type SetPassportDataErrors = $ty![SetPassportDataErrors]; + + fn set_passport_data_errors(&self, user_id: i32, errors: E) -> Self::SetPassportDataErrors where E: IntoIterator { + let this = self; + $body!(set_passport_data_errors this (user_id: i32, errors: E)) + } + }; +} diff --git a/src/requests/requester.rs b/src/requests/requester.rs index 4b31950b..573ab79f 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -1,7 +1,11 @@ use crate::{ - payloads::{GetMe, SendMessage}, + payloads::{GetMe, SendMessage, *}, requests::Request, - types::ChatId, + types::{ + AllowedUpdate, BotCommand, ChatAction, ChatId, ChatPermissions, DiceEmoji, + InlineQueryResult, InputFile, InputMedia, InputSticker, LabeledPrice, PassportElementError, + PollType, + }, }; /// The trait implemented by all bots & bot adaptors. @@ -12,18 +16,686 @@ use crate::{ pub trait Requester { type Err: std::error::Error + Send; + // This block is auto generated by `cg` (fea4d31). + // **DO NOT EDIT THIS BLOCK**, + // edit `cg` instead. + + type GetUpdates: Request; + + /// For telegram documentation see [`GetUpdates`] + fn get_updates(&self) -> Self::GetUpdates where; + + type SetWebhook: Request; + + /// For telegram documentation see [`SetWebhook`] + fn set_webhook(&self, url: U, allowed_updates: A) -> Self::SetWebhook + where + U: Into, + A: IntoIterator; + + type DeleteWebhook: Request; + + /// For telegram documentation see [`DeleteWebhook`] + fn delete_webhook(&self) -> Self::DeleteWebhook where; + + type GetWebhookInfo: Request; + + /// For telegram documentation see [`GetWebhookInfo`] + fn get_webhook_info(&self) -> Self::GetWebhookInfo where; + type GetMe: Request; - /// For telegram documentation of the method see [`GetMe`]. - fn get_me(&self) -> Self::GetMe; + /// For telegram documentation see [`GetMe`] + fn get_me(&self) -> Self::GetMe where; type SendMessage: Request; - /// For telegram documentation of the method see [`SendMessage`]. + /// For telegram documentation see [`SendMessage`] fn send_message(&self, chat_id: C, text: T) -> Self::SendMessage where C: Into, T: Into; - // FIXME(waffle): add remaining 68 methods + type ForwardMessage: Request; + + /// For telegram documentation see [`ForwardMessage`] + fn forward_message( + &self, + chat_id: C, + from_chat_id: F, + message_id: i32, + ) -> Self::ForwardMessage + where + C: Into, + F: Into; + + type SendPhoto: Request; + + /// For telegram documentation see [`SendPhoto`] + fn send_photo(&self, chat_id: Ch, photo: InputFile, caption: Ca) -> Self::SendPhoto + where + Ch: Into, + Ca: Into; + + type SendAudio: Request; + + /// For telegram documentation see [`SendAudio`] + fn send_audio(&self, chat_id: Ch, audio: InputFile, caption: Ca) -> Self::SendAudio + where + Ch: Into, + Ca: Into; + + type SendDocument: Request; + + /// For telegram documentation see [`SendDocument`] + fn send_document( + &self, + chat_id: Ch, + document: InputFile, + caption: Ca, + ) -> Self::SendDocument + where + Ch: Into, + Ca: Into; + + type SendVideo: Request; + + /// For telegram documentation see [`SendVideo`] + fn send_video(&self, chat_id: Ch, video: InputFile, caption: Ca) -> Self::SendVideo + where + Ch: Into, + Ca: Into; + + type SendAnimation: Request; + + /// For telegram documentation see [`SendAnimation`] + fn send_animation( + &self, + chat_id: Ch, + animation: InputFile, + caption: Ca, + ) -> Self::SendAnimation + where + Ch: Into, + Ca: Into; + + type SendVoice: Request; + + /// For telegram documentation see [`SendVoice`] + fn send_voice(&self, chat_id: Ch, voice: InputFile, caption: Ca) -> Self::SendVoice + where + Ch: Into, + Ca: Into; + + type SendVideoNote: Request; + + /// For telegram documentation see [`SendVideoNote`] + fn send_video_note(&self, chat_id: C, video_note: InputFile) -> Self::SendVideoNote + where + C: Into; + + type SendMediaGroup: Request; + + /// For telegram documentation see [`SendMediaGroup`] + fn send_media_group(&self, chat_id: C, media: M) -> Self::SendMediaGroup + where + C: Into, + M: IntoIterator; + + type SendLocation: Request; + + /// For telegram documentation see [`SendLocation`] + fn send_location( + &self, + chat_id: C, + latitude: f64, + longitude: f64, + live_period: u32, + ) -> Self::SendLocation + where + C: Into; + + type EditMessageLiveLocation: Request; + + /// For telegram documentation see [`EditMessageLiveLocation`] + fn edit_message_live_location( + &self, + chat_id: C, + message_id: i32, + latitude: f64, + longitude: f64, + ) -> Self::EditMessageLiveLocation + where + C: Into; + + type EditMessageLiveLocationInline: Request< + Payload = EditMessageLiveLocationInline, + Err = Self::Err, + >; + + /// For telegram documentation see [`EditMessageLiveLocationInline`] + fn edit_message_live_location_inline( + &self, + inline_message_id: I, + latitude: f64, + longitude: f64, + ) -> Self::EditMessageLiveLocationInline + where + I: Into; + + type StopMessageLiveLocation: Request; + + /// For telegram documentation see [`StopMessageLiveLocation`] + fn stop_message_live_location( + &self, + chat_id: C, + message_id: i32, + latitude: f64, + longitude: f64, + ) -> Self::StopMessageLiveLocation + where + C: Into; + + type StopMessageLiveLocationInline: Request< + Payload = StopMessageLiveLocationInline, + Err = Self::Err, + >; + + /// For telegram documentation see [`StopMessageLiveLocationInline`] + fn stop_message_live_location_inline( + &self, + inline_message_id: I, + latitude: f64, + longitude: f64, + ) -> Self::StopMessageLiveLocationInline + where + I: Into; + + type SendVenue: Request; + + /// For telegram documentation see [`SendVenue`] + fn send_venue( + &self, + chat_id: C, + latitude: f64, + longitude: f64, + title: T, + address: A, + ) -> Self::SendVenue + where + C: Into, + T: Into, + A: Into; + + type SendContact: Request; + + /// For telegram documentation see [`SendContact`] + fn send_contact(&self, chat_id: C, phone_number: f64, first_name: f64) -> Self::SendContact + where + C: Into; + + type SendPoll: Request; + + /// For telegram documentation see [`SendPoll`] + fn send_poll( + &self, + chat_id: C, + question: Q, + options: O, + type_: PollType, + ) -> Self::SendPoll + where + C: Into, + Q: Into, + O: IntoIterator; + + type SendDice: Request; + + /// For telegram documentation see [`SendDice`] + fn send_dice(&self, chat_id: C, emoji: DiceEmoji) -> Self::SendDice + where + C: Into; + + type SendChatAction: Request; + + /// For telegram documentation see [`SendChatAction`] + fn send_chat_action(&self, chat_id: C, action: ChatAction) -> Self::SendChatAction + where + C: Into; + + type GetUserProfilePhotos: Request; + + /// For telegram documentation see [`GetUserProfilePhotos`] + fn get_user_profile_photos(&self, user_id: i32) -> Self::GetUserProfilePhotos where; + + type GetFile: Request; + + /// For telegram documentation see [`GetFile`] + fn get_file(&self, file_id: F) -> Self::GetFile + where + F: Into; + + type KickChatMember: Request; + + /// For telegram documentation see [`KickChatMember`] + fn kick_chat_member(&self, chat_id: C, user_id: i32) -> Self::KickChatMember + where + C: Into; + + type UnbanChatMember: Request; + + /// For telegram documentation see [`UnbanChatMember`] + fn unban_chat_member(&self, chat_id: C, user_id: i32) -> Self::UnbanChatMember + where + C: Into; + + type RestrictChatMember: Request; + + /// For telegram documentation see [`RestrictChatMember`] + fn restrict_chat_member( + &self, + chat_id: C, + user_id: i32, + permissions: ChatPermissions, + ) -> Self::RestrictChatMember + where + C: Into; + + type PromoteChatMember: Request; + + /// For telegram documentation see [`PromoteChatMember`] + fn promote_chat_member(&self, chat_id: C, user_id: i32) -> Self::PromoteChatMember + where + C: Into; + + type SetChatAdministratorCustomTitle: Request< + Payload = SetChatAdministratorCustomTitle, + Err = Self::Err, + >; + + /// For telegram documentation see [`SetChatAdministratorCustomTitle`] + fn set_chat_administrator_custom_title( + &self, + chat_id: Ch, + user_id: i32, + custom_title: Cu, + ) -> Self::SetChatAdministratorCustomTitle + where + Ch: Into, + Cu: Into; + + type SetChatPermissions: Request; + + /// For telegram documentation see [`SetChatPermissions`] + fn set_chat_permissions( + &self, + chat_id: C, + permissions: ChatPermissions, + ) -> Self::SetChatPermissions + where + C: Into; + + type ExportChatInviteLink: Request; + + /// For telegram documentation see [`ExportChatInviteLink`] + fn export_chat_invite_link(&self, chat_id: C) -> Self::ExportChatInviteLink + where + C: Into; + + type SetChatPhoto: Request; + + /// For telegram documentation see [`SetChatPhoto`] + fn set_chat_photo(&self, chat_id: C, photo: InputFile) -> Self::SetChatPhoto + where + C: Into; + + type DeleteChatPhoto: Request; + + /// For telegram documentation see [`DeleteChatPhoto`] + fn delete_chat_photo(&self, chat_id: C) -> Self::DeleteChatPhoto + where + C: Into; + + type SetChatTitle: Request; + + /// For telegram documentation see [`SetChatTitle`] + fn set_chat_title(&self, chat_id: C, title: T) -> Self::SetChatTitle + where + C: Into, + T: Into; + + type SetChatDescription: Request; + + /// For telegram documentation see [`SetChatDescription`] + fn set_chat_description(&self, chat_id: C) -> Self::SetChatDescription + where + C: Into; + + type PinChatMessage: Request; + + /// For telegram documentation see [`PinChatMessage`] + fn pin_chat_message(&self, chat_id: C, message_id: i32) -> Self::PinChatMessage + where + C: Into; + + type UnpinChatMessage: Request; + + /// For telegram documentation see [`UnpinChatMessage`] + fn unpin_chat_message(&self, chat_id: C) -> Self::UnpinChatMessage + where + C: Into; + + type LeaveChat: Request; + + /// For telegram documentation see [`LeaveChat`] + fn leave_chat(&self, chat_id: C) -> Self::LeaveChat + where + C: Into; + + type GetChat: Request; + + /// For telegram documentation see [`GetChat`] + fn get_chat(&self, chat_id: C) -> Self::GetChat + where + C: Into; + + type GetChatAdministrators: Request; + + /// For telegram documentation see [`GetChatAdministrators`] + fn get_chat_administrators(&self, chat_id: C) -> Self::GetChatAdministrators + where + C: Into; + + type GetChatMembersCount: Request; + + /// For telegram documentation see [`GetChatMembersCount`] + fn get_chat_members_count(&self, chat_id: C) -> Self::GetChatMembersCount + where + C: Into; + + type GetChatMember: Request; + + /// For telegram documentation see [`GetChatMember`] + fn get_chat_member(&self, chat_id: C, user_id: i32) -> Self::GetChatMember + where + C: Into; + + type SetChatStickerSet: Request; + + /// For telegram documentation see [`SetChatStickerSet`] + fn set_chat_sticker_set( + &self, + chat_id: C, + sticker_set_name: S, + ) -> Self::SetChatStickerSet + where + C: Into, + S: Into; + + type DeleteChatStickerSet: Request; + + /// For telegram documentation see [`DeleteChatStickerSet`] + fn delete_chat_sticker_set(&self, chat_id: C) -> Self::DeleteChatStickerSet + where + C: Into; + + type AnswerCallbackQuery: Request; + + /// For telegram documentation see [`AnswerCallbackQuery`] + fn answer_callback_query(&self, callback_query_id: C) -> Self::AnswerCallbackQuery + where + C: Into; + + type SetMyCommands: Request; + + /// For telegram documentation see [`SetMyCommands`] + fn set_my_commands(&self, commands: C) -> Self::SetMyCommands + where + C: IntoIterator; + + type GetMyCommands: Request; + + /// For telegram documentation see [`GetMyCommands`] + fn get_my_commands(&self) -> Self::GetMyCommands where; + + type AnswerInlineQuery: Request; + + /// For telegram documentation see [`AnswerInlineQuery`] + fn answer_inline_query(&self, inline_query_id: I, results: R) -> Self::AnswerInlineQuery + where + I: Into, + R: IntoIterator; + + type EditMessageText: Request; + + /// For telegram documentation see [`EditMessageText`] + fn edit_message_text( + &self, + chat_id: C, + message_id: i32, + text: T, + ) -> Self::EditMessageText + where + C: Into, + T: Into; + + type EditMessageTextInline: Request; + + /// For telegram documentation see [`EditMessageTextInline`] + fn edit_message_text_inline( + &self, + inline_message_id: I, + text: T, + ) -> Self::EditMessageTextInline + where + I: Into, + T: Into; + + type EditMessageCaption: Request; + + /// For telegram documentation see [`EditMessageCaption`] + fn edit_message_caption( + &self, + chat_id: Ch, + message_id: i32, + caption: Ca, + ) -> Self::EditMessageCaption + where + Ch: Into, + Ca: Into; + + type EditMessageCaptionInline: Request; + + /// For telegram documentation see [`EditMessageCaptionInline`] + fn edit_message_caption_inline( + &self, + inline_message_id: I, + caption: C, + ) -> Self::EditMessageCaptionInline + where + I: Into, + C: Into; + + type EditMessageMedia: Request; + + /// For telegram documentation see [`EditMessageMedia`] + fn edit_message_media( + &self, + chat_id: C, + message_id: i32, + media: InputMedia, + ) -> Self::EditMessageMedia + where + C: Into; + + type EditMessageMediaInline: Request; + + /// For telegram documentation see [`EditMessageMediaInline`] + fn edit_message_media_inline( + &self, + inline_message_id: I, + media: InputMedia, + ) -> Self::EditMessageMediaInline + where + I: Into; + + type EditMessageReplyMarkup: Request; + + /// For telegram documentation see [`EditMessageReplyMarkup`] + fn edit_message_reply_markup( + &self, + chat_id: C, + message_id: i32, + ) -> Self::EditMessageReplyMarkup + where + C: Into; + + type EditMessageReplyMarkupInline: Request< + Payload = EditMessageReplyMarkupInline, + Err = Self::Err, + >; + + /// For telegram documentation see [`EditMessageReplyMarkupInline`] + fn edit_message_reply_markup_inline( + &self, + inline_message_id: I, + ) -> Self::EditMessageReplyMarkupInline + where + I: Into; + + type StopPoll: Request; + + /// For telegram documentation see [`StopPoll`] + fn stop_poll(&self, chat_id: C, message_id: i32) -> Self::StopPoll + where + C: Into; + + type DeleteMessage: Request; + + /// For telegram documentation see [`DeleteMessage`] + fn delete_message(&self, chat_id: C, message_id: i32) -> Self::DeleteMessage + where + C: Into; + + type SendSticker: Request; + + /// For telegram documentation see [`SendSticker`] + fn send_sticker(&self, chat_id: C, sticker: InputFile) -> Self::SendSticker + where + C: Into; + + type GetStickerSet: Request; + + /// For telegram documentation see [`GetStickerSet`] + fn get_sticker_set(&self, name: N) -> Self::GetStickerSet + where + N: Into; + + type UploadStickerFile: Request; + + /// For telegram documentation see [`UploadStickerFile`] + fn upload_sticker_file(&self, user_id: i32, png_sticker: InputFile) -> Self::UploadStickerFile where; + + type CreateNewStickerSet: Request; + + /// For telegram documentation see [`CreateNewStickerSet`] + fn create_new_sticker_set( + &self, + user_id: i32, + name: N, + title: T, + emojis: E, + ) -> Self::CreateNewStickerSet + where + N: Into, + T: Into, + E: Into; + + type AddStickerToSet: Request; + + /// For telegram documentation see [`AddStickerToSet`] + fn add_sticker_to_set( + &self, + user_id: i32, + name: N, + sticker: InputSticker, + emojis: E, + ) -> Self::AddStickerToSet + where + N: Into, + E: Into; + + type SetStickerPositionInSet: Request; + + /// For telegram documentation see [`SetStickerPositionInSet`] + fn set_sticker_position_in_set( + &self, + sticker: S, + position: u32, + ) -> Self::SetStickerPositionInSet + where + S: Into; + + type DeleteStickerFromSet: Request; + + /// For telegram documentation see [`DeleteStickerFromSet`] + fn delete_sticker_from_set(&self, sticker: S) -> Self::DeleteStickerFromSet + where + S: Into; + + type SetStickerSetThumb: Request; + + /// For telegram documentation see [`SetStickerSetThumb`] + fn set_sticker_set_thumb(&self, name: N, user_id: i32) -> Self::SetStickerSetThumb + where + N: Into; + + type SendInvoice: Request; + + /// For telegram documentation see [`SendInvoice`] + #[allow(clippy::too_many_arguments)] + fn send_invoice( + &self, + chat_id: i32, + title: T, + description: D, + payload: Pa, + provider_token: P, + start_parameter: S, + currency: C, + prices: Pri, + ) -> Self::SendInvoice + where + T: Into, + D: Into, + Pa: Into, + P: Into, + S: Into, + C: Into, + Pri: IntoIterator; + + type AnswerShippingQuery: Request; + + /// For telegram documentation see [`AnswerShippingQuery`] + fn answer_shipping_query(&self, shipping_query_id: S, ok: bool) -> Self::AnswerShippingQuery + where + S: Into; + + type AnswerPreCheckoutQuery: Request; + + /// For telegram documentation see [`AnswerPreCheckoutQuery`] + fn answer_pre_checkout_query

( + &self, + pre_checkout_query_id: P, + ok: bool, + ) -> Self::AnswerPreCheckoutQuery + where + P: Into; + + type SetPassportDataErrors: Request; + + /// For telegram documentation see [`SetPassportDataErrors`] + fn set_passport_data_errors(&self, user_id: i32, errors: E) -> Self::SetPassportDataErrors + where + E: IntoIterator; } From 7c9c871b83af854ab0c90ca9474ccf7978dacd83 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 24 Nov 2020 20:30:45 +0300 Subject: [PATCH 155/755] Remove empty generics lists, `where`s and ', ' after `self` --- src/bot/api.rs | 12 ++++++------ src/local_macros.rs | 21 ++++++++++++--------- src/requests/requester.rs | 16 ++++++++-------- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/bot/api.rs b/src/bot/api.rs index 8427ddba..fa7c5d2d 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -1709,7 +1709,7 @@ impl Requester for Bot { type GetUpdates = JsonRequest; - fn get_updates(&self) -> Self::GetUpdates where { + fn get_updates(&self) -> Self::GetUpdates { Self::GetUpdates::new(self.clone(), payloads::GetUpdates::new()) } @@ -1725,19 +1725,19 @@ impl Requester for Bot { type DeleteWebhook = JsonRequest; - fn delete_webhook(&self) -> Self::DeleteWebhook where { + fn delete_webhook(&self) -> Self::DeleteWebhook { Self::DeleteWebhook::new(self.clone(), payloads::DeleteWebhook::new()) } type GetWebhookInfo = JsonRequest; - fn get_webhook_info(&self) -> Self::GetWebhookInfo where { + fn get_webhook_info(&self) -> Self::GetWebhookInfo { Self::GetWebhookInfo::new(self.clone(), payloads::GetWebhookInfo::new()) } type GetMe = JsonRequest; - fn get_me(&self) -> Self::GetMe where { + fn get_me(&self) -> Self::GetMe { Self::GetMe::new(self.clone(), payloads::GetMe::new()) } @@ -2029,7 +2029,7 @@ impl Requester for Bot { type GetUserProfilePhotos = JsonRequest; - fn get_user_profile_photos(&self, user_id: i32) -> Self::GetUserProfilePhotos where { + fn get_user_profile_photos(&self, user_id: i32) -> Self::GetUserProfilePhotos { Self::GetUserProfilePhotos::new(self.clone(), payloads::GetUserProfilePhotos::new(user_id)) } @@ -2280,7 +2280,7 @@ impl Requester for Bot { type GetMyCommands = JsonRequest; - fn get_my_commands(&self) -> Self::GetMyCommands where { + fn get_my_commands(&self) -> Self::GetMyCommands { Self::GetMyCommands::new(self.clone(), payloads::GetMyCommands::new()) } diff --git a/src/local_macros.rs b/src/local_macros.rs index fc522950..83bf0f57 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -367,7 +367,7 @@ macro_rules! impl_payload { } #[macro_use] -// This macro is auto generated by `cg` (fea4d31). +// This macro is auto generated by `cg` (a8fa55a). // **DO NOT EDIT THIS MACRO**, // edit `cg` instead. macro_rules! requester_forward { @@ -377,10 +377,12 @@ macro_rules! requester_forward { requester_forward!(@method $rest $body $ty); )* }; + + (@method get_updates $body:ident $ty:ident) => { type GetUpdates = $ty![GetUpdates]; - fn get_updates<>(&self, ) -> Self::GetUpdates where { + fn get_updates(&self) -> Self::GetUpdates { let this = self; $body!(get_updates this ()) } @@ -397,7 +399,7 @@ macro_rules! requester_forward { (@method delete_webhook $body:ident $ty:ident) => { type DeleteWebhook = $ty![DeleteWebhook]; - fn delete_webhook<>(&self, ) -> Self::DeleteWebhook where { + fn delete_webhook(&self) -> Self::DeleteWebhook { let this = self; $body!(delete_webhook this ()) } @@ -405,7 +407,7 @@ macro_rules! requester_forward { (@method get_webhook_info $body:ident $ty:ident) => { type GetWebhookInfo = $ty![GetWebhookInfo]; - fn get_webhook_info<>(&self, ) -> Self::GetWebhookInfo where { + fn get_webhook_info(&self) -> Self::GetWebhookInfo { let this = self; $body!(get_webhook_info this ()) } @@ -413,7 +415,7 @@ macro_rules! requester_forward { (@method get_me $body:ident $ty:ident) => { type GetMe = $ty![GetMe]; - fn get_me<>(&self, ) -> Self::GetMe where { + fn get_me(&self) -> Self::GetMe { let this = self; $body!(get_me this ()) } @@ -594,7 +596,7 @@ macro_rules! requester_forward { (@method get_user_profile_photos $body:ident $ty:ident) => { type GetUserProfilePhotos = $ty![GetUserProfilePhotos]; - fn get_user_profile_photos<>(&self, user_id: i32) -> Self::GetUserProfilePhotos where { + fn get_user_profile_photos(&self, user_id: i32) -> Self::GetUserProfilePhotos { let this = self; $body!(get_user_profile_photos this (user_id: i32)) } @@ -789,7 +791,7 @@ macro_rules! requester_forward { (@method get_my_commands $body:ident $ty:ident) => { type GetMyCommands = $ty![GetMyCommands]; - fn get_my_commands<>(&self, ) -> Self::GetMyCommands where { + fn get_my_commands(&self) -> Self::GetMyCommands { let this = self; $body!(get_my_commands this ()) } @@ -906,7 +908,7 @@ macro_rules! requester_forward { (@method upload_sticker_file $body:ident $ty:ident) => { type UploadStickerFile = $ty![UploadStickerFile]; - fn upload_sticker_file<>(&self, user_id: i32, png_sticker: InputFile) -> Self::UploadStickerFile where { + fn upload_sticker_file(&self, user_id: i32, png_sticker: InputFile) -> Self::UploadStickerFile { let this = self; $body!(upload_sticker_file this (user_id: i32, png_sticker: InputFile)) } @@ -937,7 +939,8 @@ macro_rules! requester_forward { let this = self; $body!(set_sticker_position_in_set this (sticker: S, position: u32)) } - };(@method delete_sticker_from_set $body:ident $ty:ident) => { + }; + (@method delete_sticker_from_set $body:ident $ty:ident) => { type DeleteStickerFromSet = $ty![DeleteStickerFromSet]; fn delete_sticker_from_set(&self, sticker: S) -> Self::DeleteStickerFromSet where S: Into { diff --git a/src/requests/requester.rs b/src/requests/requester.rs index 573ab79f..5352ab35 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -16,14 +16,14 @@ use crate::{ pub trait Requester { type Err: std::error::Error + Send; - // This block is auto generated by `cg` (fea4d31). + // This block is auto generated by `cg` (a8fa55a). // **DO NOT EDIT THIS BLOCK**, // edit `cg` instead. type GetUpdates: Request; /// For telegram documentation see [`GetUpdates`] - fn get_updates(&self) -> Self::GetUpdates where; + fn get_updates(&self) -> Self::GetUpdates; type SetWebhook: Request; @@ -36,17 +36,17 @@ pub trait Requester { type DeleteWebhook: Request; /// For telegram documentation see [`DeleteWebhook`] - fn delete_webhook(&self) -> Self::DeleteWebhook where; + fn delete_webhook(&self) -> Self::DeleteWebhook; type GetWebhookInfo: Request; /// For telegram documentation see [`GetWebhookInfo`] - fn get_webhook_info(&self) -> Self::GetWebhookInfo where; + fn get_webhook_info(&self) -> Self::GetWebhookInfo; type GetMe: Request; /// For telegram documentation see [`GetMe`] - fn get_me(&self) -> Self::GetMe where; + fn get_me(&self) -> Self::GetMe; type SendMessage: Request; @@ -266,7 +266,7 @@ pub trait Requester { type GetUserProfilePhotos: Request; /// For telegram documentation see [`GetUserProfilePhotos`] - fn get_user_profile_photos(&self, user_id: i32) -> Self::GetUserProfilePhotos where; + fn get_user_profile_photos(&self, user_id: i32) -> Self::GetUserProfilePhotos; type GetFile: Request; @@ -456,7 +456,7 @@ pub trait Requester { type GetMyCommands: Request; /// For telegram documentation see [`GetMyCommands`] - fn get_my_commands(&self) -> Self::GetMyCommands where; + fn get_my_commands(&self) -> Self::GetMyCommands; type AnswerInlineQuery: Request; @@ -594,7 +594,7 @@ pub trait Requester { type UploadStickerFile: Request; /// For telegram documentation see [`UploadStickerFile`] - fn upload_sticker_file(&self, user_id: i32, png_sticker: InputFile) -> Self::UploadStickerFile where; + fn upload_sticker_file(&self, user_id: i32, png_sticker: InputFile) -> Self::UploadStickerFile; type CreateNewStickerSet: Request; From 45cf62ce8e0fd1e7892587512b8a9b25c8341f9d Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 27 Nov 2020 01:18:57 +0300 Subject: [PATCH 156/755] Refactor Bot for a bit - Move default parse mode to an adaptor - Remove bot builder (it's not usefull anymore, since parse_mode is moved away) - Undeprecate bot constructors --- src/adaptors.rs | 4 + src/adaptors/parse_mode.rs | 108 ++++++++++++++++++ src/bot/api.rs | 79 +++---------- src/bot/mod.rs | 205 ++++++++-------------------------- src/lib.rs | 6 +- src/requests/requester_ext.rs | 34 +++++- 6 files changed, 207 insertions(+), 229 deletions(-) create mode 100644 src/adaptors/parse_mode.rs diff --git a/src/adaptors.rs b/src/adaptors.rs index 09c4fec8..40982784 100644 --- a/src/adaptors.rs +++ b/src/adaptors.rs @@ -19,6 +19,8 @@ pub mod cache_me; #[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "throttle")))] pub mod throttle; +mod parse_mode; + #[cfg(feature = "auto_send")] #[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "auto_send")))] pub use auto_send::AutoSend; @@ -29,4 +31,6 @@ pub use cache_me::CacheMe; #[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "throttle")))] pub use throttle::Throttle; +pub use parse_mode::DefaultParseMode; + // FIXME: move default `parse_mode` to adaptor diff --git a/src/adaptors/parse_mode.rs b/src/adaptors/parse_mode.rs new file mode 100644 index 00000000..e191e73d --- /dev/null +++ b/src/adaptors/parse_mode.rs @@ -0,0 +1,108 @@ +use crate::{ + prelude::Requester, + requests::HasPayload, + types::{ChatId, InputFile, ParseMode, *}, +}; + +/// Default parse mode adaptor, see +/// [`RequesterExt::parse_mode`](crate::requests::RequesterExt::parse_mode) +pub struct DefaultParseMode { + bot: B, + mode: ParseMode, +} + +impl DefaultParseMode { + /// Creates new [`DefaultParseMode`] + /// + /// Note: it's recommended to use [`RequesterExt::parse_mode`] instead. + /// + /// [`RequesterExt::parse_mode`]: crate::requests::RequesterExt::parse_mode + pub fn new(bot: B, parse_mode: ParseMode) -> Self { + Self { bot, mode: parse_mode } + } + + /// Allows to access inner bot + pub fn inner(&self) -> &B { + &self.bot + } + + /// Unwraps inner bot + pub fn into_inner(self) -> B { + self.bot + } +} + +macro_rules! f { + ($m:ident $this:ident ($($arg:ident : $T:ty),*)) => { + { + let mut req = $this.inner().$m($($arg),*); + req.payload_mut().parse_mode = Some($this.mode); + req + } + }; +} + +macro_rules! fty { + ($T:ident) => { + B::$T + }; +} + +macro_rules! fid { + ($m:ident $this:ident ($($arg:ident : $T:ty),*)) => { + $this.inner().$m($($arg),*) + }; +} + +impl Requester for DefaultParseMode { + type Err = B::Err; + + requester_forward! { + send_message, + send_photo, + send_video, + send_audio, + send_document, + send_animation, + send_voice, + edit_message_text, + edit_message_text_inline, + edit_message_caption, + edit_message_caption_inline => f, fty + } + + type SendPoll = B::SendPoll; + + fn send_poll( + &self, + chat_id: C, + question: Q, + options: O, + type_: PollType, + ) -> Self::SendPoll + where + C: Into, + Q: Into, + O: IntoIterator, + { + let mut req = self.inner().send_poll(chat_id, question, options, type_); + req.payload_mut().explanation_parse_mode = Some(self.mode); + req + } + + requester_forward! { + get_me, get_updates, set_webhook, delete_webhook, get_webhook_info, + forward_message, send_video_note, send_media_group, send_location, + edit_message_live_location, edit_message_live_location_inline, stop_message_live_location, + stop_message_live_location_inline, send_venue, send_contact, send_dice, + send_chat_action, get_user_profile_photos, get_file, kick_chat_member, unban_chat_member, + restrict_chat_member, promote_chat_member, set_chat_administrator_custom_title, set_chat_permissions, + export_chat_invite_link, set_chat_photo, delete_chat_photo, set_chat_title, set_chat_description, + pin_chat_message, unpin_chat_message, leave_chat, get_chat, get_chat_administrators, get_chat_members_count, + get_chat_member, set_chat_sticker_set, delete_chat_sticker_set, answer_callback_query, set_my_commands, + get_my_commands, answer_inline_query, edit_message_media, edit_message_media_inline, edit_message_reply_markup, + edit_message_reply_markup_inline, stop_poll, delete_message, send_sticker, get_sticker_set, upload_sticker_file, + create_new_sticker_set, add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, + set_sticker_set_thumb, send_invoice, answer_shipping_query, answer_pre_checkout_query, set_passport_data_errors => fid, fty + } +} diff --git a/src/bot/api.rs b/src/bot/api.rs index fa7c5d2d..9bebc7bb 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -21,7 +21,7 @@ use crate::{ }, types::{ BotCommand, ChatId, ChatPermissions, InlineQueryResult, InputFile, InputMedia, - InputSticker, LabeledPrice, ParseMode, TargetMessage, + InputSticker, LabeledPrice, TargetMessage, }, Bot, }; @@ -130,10 +130,7 @@ impl Bot { C: Into, T: Into, { - self.with_default_parse_mode_if_specified( - SendMessage::new(self.clone(), chat_id, text), - SendMessage::parse_mode, - ) + SendMessage::new(self.clone(), chat_id, text) } /// Use this method to forward messages of any kind. @@ -192,10 +189,7 @@ impl Bot { where C: Into, { - self.with_default_parse_mode_if_specified( - SendPhoto::new(self.clone(), chat_id, photo), - SendPhoto::parse_mode, - ) + SendPhoto::new(self.clone(), chat_id, photo) } /// @@ -213,10 +207,7 @@ impl Bot { where C: Into, { - self.with_default_parse_mode_if_specified( - SendAudio::new(self.clone(), chat_id, audio), - SendAudio::parse_mode, - ) + SendAudio::new(self.clone(), chat_id, audio) } /// Use this method to send general files. @@ -247,10 +238,7 @@ impl Bot { where C: Into, { - self.with_default_parse_mode_if_specified( - SendDocument::new(self.clone(), chat_id, document), - SendDocument::parse_mode, - ) + SendDocument::new(self.clone(), chat_id, document) } /// Use this method to send video files, Telegram clients support mp4 videos @@ -284,10 +272,7 @@ impl Bot { where C: Into, { - self.with_default_parse_mode_if_specified( - SendVideo::new(self.clone(), chat_id, video), - SendVideo::parse_mode, - ) + SendVideo::new(self.clone(), chat_id, video) } /// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video @@ -312,10 +297,7 @@ impl Bot { where C: Into, { - self.with_default_parse_mode_if_specified( - SendAnimation::new(self.clone(), chat_id, animation), - SendAnimation::parse_mode, - ) + SendAnimation::new(self.clone(), chat_id, animation) } /// Use this method to send audio files, if you want Telegram clients to @@ -355,10 +337,7 @@ impl Bot { where C: Into, { - self.with_default_parse_mode_if_specified( - SendVoice::new(self.clone(), chat_id, voice), - SendVoice::parse_mode, - ) + SendVoice::new(self.clone(), chat_id, voice) } /// As of [v.4.0], Telegram clients support rounded square mp4 videos of up @@ -583,10 +562,7 @@ impl Bot { Q: Into, O: Into>, { - self.with_default_parse_mode_if_specified( - SendPoll::new(self.clone(), chat_id, question, options), - SendPoll::explanation_parse_mode, - ) + SendPoll::new(self.clone(), chat_id, question, options) } /// Use this method when you need to tell the user that something is @@ -1069,12 +1045,7 @@ impl Bot { C: Into, T: Into, { - match self.parse_mode { - None => EditMessageText::new(self.clone(), chat_id, message_id, text), - Some(parse_mode) => { - EditMessageText::new(self.clone(), chat_id, message_id, text).parse_mode(parse_mode) - } - } + EditMessageText::new(self.clone(), chat_id, message_id, text) } /// Use this method to edit text and game messages sent via the bot. @@ -1105,11 +1076,7 @@ impl Bot { I: Into, T: Into, { - match self.parse_mode { - None => EditInlineMessageText::new(self.clone(), inline_message_id, text), - Some(parse_mode) => EditInlineMessageText::new(self.clone(), inline_message_id, text) - .parse_mode(parse_mode), - } + EditInlineMessageText::new(self.clone(), inline_message_id, text) } /// Use this method to edit captions of messages sent via the bot. @@ -1130,12 +1097,7 @@ impl Bot { where C: Into, { - match self.parse_mode { - None => EditMessageCaption::new(self.clone(), chat_id, message_id), - Some(parse_mode) => { - EditMessageCaption::new(self.clone(), chat_id, message_id).parse_mode(parse_mode) - } - } + EditMessageCaption::new(self.clone(), chat_id, message_id) } /// Use this method to edit captions of messages sent via the bot. @@ -1155,11 +1117,7 @@ impl Bot { where I: Into, { - match self.parse_mode { - None => EditInlineMessageCaption::new(self.clone(), inline_message_id), - Some(parse_mode) => EditInlineMessageCaption::new(self.clone(), inline_message_id) - .parse_mode(parse_mode), - } + EditInlineMessageCaption::new(self.clone(), inline_message_id) } /// Use this method to edit animation, audio, document, photo, or video @@ -1691,17 +1649,6 @@ impl Bot { { SetStickerSetThumb::new(self.clone(), name, user_id) } - - fn with_default_parse_mode_if_specified( - &self, - builder: Builder, - f: fn(Builder, ParseMode) -> Builder, - ) -> Builder { - match self.parse_mode { - None => builder, - Some(parse_mode) => f(builder, parse_mode), - } - } } impl Requester for Bot { diff --git a/src/bot/mod.rs b/src/bot/mod.rs index bdba198b..6187cf41 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -11,7 +11,6 @@ use crate::{ net, requests::{Payload, ResponseResult}, serde_multipart, - types::ParseMode, }; mod api; @@ -31,56 +30,15 @@ pub struct Bot { token: Arc, api_url: ApiUrl, client: Client, - parse_mode: Option, } impl Bot { - /// Creates a new `Bot` with the `TELOXIDE_TOKEN` & `TELOXIDE_PROXY` - /// environmental variables (a bot's token & a proxy) and the default - /// [`reqwest::Client`]. - /// - /// This function passes the value of `TELOXIDE_PROXY` into - /// [`reqwest::Proxy::all`], if it exists, otherwise returns the default - /// client. - /// - /// # Panics - /// - If cannot get the `TELOXIDE_TOKEN` environmental variable. - /// - If it cannot create [`reqwest::Client`]. - /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html - /// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all - #[must_use] - pub fn from_env() -> Self { - BotBuilder::new().build() - } - - /// 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. - /// - /// # Caution - /// Your custom client might not be configured correctly to be able to work - /// in long time durations, see [issue 223]. - /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html - /// [issue 223]: https://github.com/teloxide/teloxide/issues/223 - #[deprecated] - #[allow(deprecated)] - pub fn from_env_with_client(client: Client) -> Self { - Self::with_client(&get_env(TELOXIDE_TOKEN), client) - } - /// Creates a new `Bot` with the specified token and the default - /// [`reqwest::Client`]. + /// [http-client](reqwest::Client). /// /// # Panics - /// If it cannot create [`reqwest::Client`]. /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html - #[deprecated] - #[allow(deprecated)] + /// If it cannot create [`reqwest::Client`]. pub fn new(token: S) -> Self where S: Into, @@ -97,8 +55,6 @@ impl Bot { /// /// [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html /// [issue 223]: https://github.com/teloxide/teloxide/issues/223 - #[deprecated] - #[allow(deprecated)] pub fn with_client(token: S, client: Client) -> Self where S: Into, @@ -107,9 +63,52 @@ impl Bot { token: Into::>::into(Into::::into(token)), api_url: ApiUrl::Default, client, - parse_mode: None, } } + + /// Creates a new `Bot` with the `TELOXIDE_TOKEN` & `TELOXIDE_PROXY` + /// environmental variables (a bot's token & a proxy) and the default + /// [`reqwest::Client`]. + /// + /// This function passes the value of `TELOXIDE_PROXY` into + /// [`reqwest::Proxy::all`], if it exists, otherwise returns the default + /// client. + /// + /// # Panics + /// - If cannot get the `TELOXIDE_TOKEN` environmental variable. + /// - If it cannot create [`reqwest::Client`]. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + /// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all + pub fn from_env() -> Self { + Self::from_env_with_client(crate::client_from_env()) + } + + /// 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. + /// + /// # Caution + /// Your custom client might not be configured correctly to be able to work + /// in long time durations, see [issue 223]. + /// + /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html + /// [issue 223]: https://github.com/teloxide/teloxide/issues/223 + pub fn from_env_with_client(client: Client) -> Self { + Self::with_client(&get_env(TELOXIDE_TOKEN), client) + } + + /// Returns currently used token + pub fn token(&self) -> &str { + &self.token + } + + /// Returns currently used http-client + pub fn client(&self) -> &Client { + &self.client + } } impl Bot { @@ -182,115 +181,3 @@ pub(crate) fn build_sound_bot() -> Client { fn get_env(env: &'static str) -> String { std::env::var(env).unwrap_or_else(|_| panic!("Cannot get the {} env variable", env)) } - -impl Bot { - pub fn token(&self) -> &str { - &self.token - } - - pub fn client(&self) -> &Client { - &self.client - } -} - -/// A builder of [`Bot`], supporting some extra settings. -/// -/// [`Bot`] crate::Bot -#[derive(Debug, Default)] -pub struct BotBuilder { - token: Option, - client: Option, - parse_mode: Option, -} - -impl BotBuilder { - #[must_use] - pub fn new() -> Self { - Self::default() - } - - /// Specifies a custom HTTPS client. Otherwise, the default will be used. - /// - /// # Caution - /// - Your custom client might not be configured correctly to be able to - /// work - /// in long time durations, see [issue 223]. - /// - /// - If this method is used, the `TELOXIDE_PROXY` environmental variable - /// won't be extracted in [`BotBuilder::build`]. - /// - /// [issue 223]: https://github.com/teloxide/teloxide/issues/223 - /// [`BotBuilder::build`]: crate::BotBuilder::build - #[must_use] - pub fn client(mut self, client: Client) -> Self { - self.client = Some(client); - self - } - - /// Specified a custom token. - /// - /// Otherwise, a token will be extracted from the `TELOXIDE_TOKEN` - /// environmental variable. - #[must_use] - pub fn token(mut self, token: S) -> Self - where - S: Into, - { - self.token = Some(token.into()); - self - } - - /// Specifies [`ParseMode`], which will be used during all calls to: - /// - /// - [`send_message`] - /// - [`send_photo`] - /// - [`send_video`] - /// - [`send_audio`] - /// - [`send_document`] - /// - [`send_animation`] - /// - [`send_voice`] - /// - [`send_poll`] - /// - [`edit_message_text`] - /// - [`edit_message_caption`] - /// - /// [`send_message`]: crate::Bot::send_message - /// [`send_photo`]: crate::Bot::send_photo - /// [`send_video`]: crate::Bot::send_video - /// [`send_audio`]: crate::Bot::send_audio - /// [`send_document`]: crate::Bot::send_document - /// [`send_animation`]: crate::Bot::send_animation - /// [`send_voice`]: crate::Bot::send_voice - /// [`send_poll`]: crate::Bot::send_poll - /// [`edit_message_text`]: crate::Bot::edit_message_text - /// [`edit_message_caption`]: crate::Bot::edit_message_caption - #[must_use] - pub fn parse_mode(mut self, parse_mode: ParseMode) -> Self { - self.parse_mode = Some(parse_mode); - self - } - - /// Builds [`Bot`]. - /// - /// This method will attempt to build a new client with a proxy, specified - /// in the `TELOXIDE_PROXY` (passed into [`reqwest::Proxy::all`]) - /// environmental variable, if a client haven't been specified. If - /// `TELOXIDE_PROXY` is unspecified, it'll use no proxy. - /// - /// # Panics - /// - If cannot get the `TELOXIDE_TOKEN` environmental variable. - /// - If it cannot create [`reqwest::Client`]. - /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html - /// - /// [`Bot`]: crate::Bot - /// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all - #[must_use] - pub fn build(self) -> Bot { - Bot { - token: self.token.unwrap_or_else(|| get_env(TELOXIDE_TOKEN)).into(), - api_url: ApiUrl::Default, - client: self.client.unwrap_or_else(crate::client_from_env), - parse_mode: self.parse_mode, - } - } -} diff --git a/src/lib.rs b/src/lib.rs index ef19a9c9..54d79c30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,19 +7,19 @@ // ```console // $ RUSTDOCFLAGS="--cfg docsrs" cargo doc --open --all-features // ``` +#![forbid(unsafe_code)] #![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg, doc_spotlight))] #![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))] -#![forbid(unsafe_code)] #![cfg_attr(feature = "full", deny(broken_intra_doc_links))] //#![deny(missing_docs)] -#[macro_use] // The internal helper macros. +#[macro_use] mod local_macros; // FIXME(waffle): rethink modules, find a place for wrappers. pub use self::{ - bot::{Bot, BotBuilder}, + bot::Bot, errors::{ApiError, DownloadError, RequestError}, }; diff --git a/src/requests/requester_ext.rs b/src/requests/requester_ext.rs index 1b5530b2..f819b0f3 100644 --- a/src/requests/requester_ext.rs +++ b/src/requests/requester_ext.rs @@ -1,4 +1,4 @@ -use crate::requests::Requester; +use crate::{adaptors::DefaultParseMode, requests::Requester, types::ParseMode}; #[cfg(feature = "cache_me")] use crate::adaptors::CacheMe; @@ -46,6 +46,38 @@ pub trait RequesterExt: Requester { { Throttle::new_spawn(self, limits) } + + /// Specifies default [`ParseMode`], which will be used during all calls to: + /// + /// - [`send_message`] + /// - [`send_photo`] + /// - [`send_video`] + /// - [`send_audio`] + /// - [`send_document`] + /// - [`send_animation`] + /// - [`send_voice`] + /// - [`send_poll`] + /// - [`edit_message_text`] (and [`edit_message_text_inline`]) + /// - [`edit_message_caption`] (and [`edit_message_caption_inline`]) + /// + /// [`send_message`]: crate::Requester::send_message + /// [`send_photo`]: crate::Requester::send_photo + /// [`send_video`]: crate::Requester::send_video + /// [`send_audio`]: crate::Requester::send_audio + /// [`send_document`]: crate::Requester::send_document + /// [`send_animation`]: crate::Requester::send_animation + /// [`send_voice`]: crate::Requester::send_voice + /// [`send_poll`]: crate::Requester::send_poll + /// [`edit_message_text`]: crate::Requester::edit_message_text + /// [`edit_message_text`]: crate::Requester::edit_message_text_inline + /// [`edit_message_caption`]: crate::Requester::edit_message_caption + /// [`edit_message_caption`]: crate::Requester::edit_message_caption_inline + fn parse_mode(self, parse_mode: ParseMode) -> DefaultParseMode + where + Self: Sized, + { + DefaultParseMode::new(self, parse_mode) + } } impl RequesterExt for T From eb45ac7d884488e158a07e2bf0637d70dfbf31bc Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 27 Nov 2020 01:39:35 +0300 Subject: [PATCH 157/755] Fix doc links --- src/bot/api.rs | 79 ----------------------------------- src/requests/requester_ext.rs | 27 ++++++------ 2 files changed, 15 insertions(+), 91 deletions(-) diff --git a/src/bot/api.rs b/src/bot/api.rs index 9bebc7bb..9facaba9 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -119,12 +119,6 @@ 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. - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder pub fn send_message(&self, chat_id: C, text: T) -> SendMessage where C: Into, @@ -179,12 +173,6 @@ impl Bot { /// [`InputFile::FileId`]: crate::types::InputFile::FileId /// /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder pub fn send_photo(&self, chat_id: C, photo: InputFile) -> SendPhoto where C: Into, @@ -197,12 +185,6 @@ impl Bot { /// # Params /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder pub fn send_audio(&self, chat_id: C, audio: InputFile) -> SendAudio where C: Into, @@ -228,12 +210,6 @@ impl Bot { /// `multipart/form-data`. [More info on Sending Files Âģ]. /// /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder pub fn send_document(&self, chat_id: C, document: InputFile) -> SendDocument where C: Into, @@ -262,12 +238,6 @@ impl Bot { /// [`InputFile::File`]: crate::types::InputFile::File /// [`InputFile::Url`]: crate::types::InputFile::Url /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder pub fn send_video(&self, chat_id: C, video: InputFile) -> SendVideo where C: Into, @@ -287,12 +257,6 @@ impl Bot { /// - `chat_id`: Unique identifier for the target chat or username of the /// target supergroup or channel (in the format `@channelusername`). /// - `animation`: Animation to send. - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder pub fn send_animation(&self, chat_id: C, animation: InputFile) -> SendAnimation where C: Into, @@ -327,12 +291,6 @@ 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 - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder pub fn send_voice(&self, chat_id: C, voice: InputFile) -> SendVoice where C: Into, @@ -546,16 +504,6 @@ impl Bot { /// - `question`: Poll question, 1-255 characters. /// - `options`: List of answer options, 2-10 strings 1-100 characters /// each. - /// - /// # Notes - /// Uses [a default parse mode] ([`SendPoll::explanation_parse_mode`]) if - /// specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - /// [`SendPoll::explanation_parse_mode`]: - /// [`SendPoll::explanation_parse_mode`]: - /// crate::types::SendPoll::explanation_parse_mode pub fn send_poll(&self, chat_id: C, question: Q, options: O) -> SendPoll where C: Into, @@ -1033,13 +981,6 @@ impl Bot { /// target channel (in the format `@channelusername`). /// - `message_id`: Identifier of the message to edit. /// - `text`: New text of the message. - /// - /// # Notes - /// - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder pub fn edit_message_text(&self, chat_id: C, message_id: i32, text: T) -> EditMessageText where C: Into, @@ -1060,13 +1001,6 @@ impl Bot { /// /// - `inline_message_id`: Identifier of the inline message. /// - `text`: New text of the message. - /// - /// # Notes - /// - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder pub fn edit_inline_message_text( &self, inline_message_id: I, @@ -1086,13 +1020,6 @@ impl Bot { /// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). /// /// [`True`]: crate::types::True - /// - /// # Notes - /// - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder pub fn edit_message_caption(&self, chat_id: C, message_id: i32) -> EditMessageCaption where C: Into, @@ -1107,12 +1034,6 @@ impl Bot { /// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). /// /// [`True`]: crate::types::True - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder pub fn edit_inline_message_caption(&self, inline_message_id: I) -> EditInlineMessageCaption where I: Into, diff --git a/src/requests/requester_ext.rs b/src/requests/requester_ext.rs index f819b0f3..57ef9a52 100644 --- a/src/requests/requester_ext.rs +++ b/src/requests/requester_ext.rs @@ -60,18 +60,21 @@ pub trait RequesterExt: Requester { /// - [`edit_message_text`] (and [`edit_message_text_inline`]) /// - [`edit_message_caption`] (and [`edit_message_caption_inline`]) /// - /// [`send_message`]: crate::Requester::send_message - /// [`send_photo`]: crate::Requester::send_photo - /// [`send_video`]: crate::Requester::send_video - /// [`send_audio`]: crate::Requester::send_audio - /// [`send_document`]: crate::Requester::send_document - /// [`send_animation`]: crate::Requester::send_animation - /// [`send_voice`]: crate::Requester::send_voice - /// [`send_poll`]: crate::Requester::send_poll - /// [`edit_message_text`]: crate::Requester::edit_message_text - /// [`edit_message_text`]: crate::Requester::edit_message_text_inline - /// [`edit_message_caption`]: crate::Requester::edit_message_caption - /// [`edit_message_caption`]: crate::Requester::edit_message_caption_inline + /// [`send_message`]: crate::requests::Requester::send_message + /// [`send_photo`]: crate::requests::Requester::send_photo + /// [`send_video`]: crate::requests::Requester::send_video + /// [`send_audio`]: crate::requests::Requester::send_audio + /// [`send_document`]: crate::requests::Requester::send_document + /// [`send_animation`]: crate::requests::Requester::send_animation + /// [`send_voice`]: crate::requests::Requester::send_voice + /// [`send_poll`]: crate::requests::Requester::send_poll + /// [`edit_message_text`]: crate::requests::Requester::edit_message_text + /// [`edit_message_text_inline`]: + /// crate::requests::Requester::edit_message_text_inline + /// [`edit_message_caption`]: + /// crate::requests::Requester::edit_message_caption + /// [`edit_message_caption_inline`]: + /// crate::requests::Requester::edit_message_caption_inline fn parse_mode(self, parse_mode: ParseMode) -> DefaultParseMode where Self: Sized, From 84ac0212e819fbdccdff4b5323d14d40ea742632 Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 26 Nov 2020 12:28:52 +0300 Subject: [PATCH 158/755] Remove legacy code --- src/bot/api.rs | 1569 +---------------- src/bot/mod.rs | 4 +- src/net/mod.rs | 2 +- src/net/request.rs | 75 +- src/requests/all/add_sticker_to_set.rs | 96 - src/requests/all/answer_callback_query.rs | 101 -- src/requests/all/answer_inline_query.rs | 146 -- src/requests/all/answer_pre_checkout_query.rs | 82 - src/requests/all/answer_shipping_query.rs | 86 - src/requests/all/create_new_sticker_set.rs | 120 -- src/requests/all/delete_chat_photo.rs | 50 - src/requests/all/delete_chat_sticker_set.rs | 55 - src/requests/all/delete_message.rs | 67 - src/requests/all/delete_sticker_from_set.rs | 47 - src/requests/all/delete_webhook.rs | 37 - .../all/edit_inline_message_caption.rs | 83 - .../all/edit_inline_message_live_location.rs | 77 - src/requests/all/edit_inline_message_media.rs | 72 - .../all/edit_inline_message_reply_markup.rs | 62 - src/requests/all/edit_inline_message_text.rs | 98 - src/requests/all/edit_message_caption.rs | 91 - .../all/edit_message_live_location.rs | 91 - src/requests/all/edit_message_media.rs | 78 - src/requests/all/edit_message_reply_markup.rs | 69 - src/requests/all/edit_message_text.rs | 107 -- src/requests/all/export_chat_invite_link.rs | 65 - src/requests/all/forward_message.rs | 81 - src/requests/all/get_chat.rs | 50 - src/requests/all/get_chat_administrators.rs | 53 - src/requests/all/get_chat_member.rs | 55 - src/requests/all/get_chat_members_count.rs | 48 - src/requests/all/get_file.rs | 62 - src/requests/all/get_game_high_scores.rs | 63 - src/requests/all/get_me.rs | 33 - src/requests/all/get_my_commands.rs | 33 - src/requests/all/get_sticker_set.rs | 47 - src/requests/all/get_updates.rs | 106 -- src/requests/all/get_updates_non_strict.rs | 97 - src/requests/all/get_user_profile_photos.rs | 58 - src/requests/all/get_webhook_info.rs | 38 - src/requests/all/kick_chat_member.rs | 72 - src/requests/all/leave_chat.rs | 48 - src/requests/all/mod.rs | 154 -- src/requests/all/pin_chat_message.rs | 69 - src/requests/all/promote_chat_member.rs | 134 -- src/requests/all/restrict_chat_member.rs | 76 - src/requests/all/send_animation.rs | 158 -- src/requests/all/send_audio.rs | 183 -- src/requests/all/send_chat_action.rs | 105 -- src/requests/all/send_contact.rs | 129 -- src/requests/all/send_dice.rs | 93 - src/requests/all/send_document.rs | 140 -- src/requests/all/send_game.rs | 92 - src/requests/all/send_invoice.rs | 299 ---- src/requests/all/send_location.rs | 110 -- src/requests/all/send_media_group.rs | 78 - src/requests/all/send_message.rs | 121 -- src/requests/all/send_photo.rs | 127 -- src/requests/all/send_poll.rs | 217 --- src/requests/all/send_sticker.rs | 104 -- src/requests/all/send_venue.rs | 159 -- src/requests/all/send_video.rs | 182 -- src/requests/all/send_video_note.rs | 138 -- src/requests/all/send_voice.rs | 144 -- .../set_chat_administrator_custom_title.rs | 75 - src/requests/all/set_chat_description.rs | 62 - src/requests/all/set_chat_permissions.rs | 58 - src/requests/all/set_chat_photo.rs | 58 - src/requests/all/set_chat_sticker_set.rs | 64 - src/requests/all/set_chat_title.rs | 63 - src/requests/all/set_game_score.rs | 89 - src/requests/all/set_my_commands.rs | 49 - .../all/set_sticker_position_in_set.rs | 56 - src/requests/all/set_sticker_set_thumb.rs | 73 - src/requests/all/set_webhook.rs | 114 -- .../all/stop_inline_message_live_location.rs | 62 - .../all/stop_message_live_location.rs | 70 - src/requests/all/stop_poll.rs | 66 - src/requests/all/unban_chat_member.rs | 58 - src/requests/all/unpin_chat_message.rs | 52 - src/requests/all/upload_sticker_file.rs | 55 - src/requests/mod.rs | 39 +- 82 files changed, 22 insertions(+), 8527 deletions(-) delete mode 100644 src/requests/all/add_sticker_to_set.rs delete mode 100644 src/requests/all/answer_callback_query.rs delete mode 100644 src/requests/all/answer_inline_query.rs delete mode 100644 src/requests/all/answer_pre_checkout_query.rs delete mode 100644 src/requests/all/answer_shipping_query.rs delete mode 100644 src/requests/all/create_new_sticker_set.rs delete mode 100644 src/requests/all/delete_chat_photo.rs delete mode 100644 src/requests/all/delete_chat_sticker_set.rs delete mode 100644 src/requests/all/delete_message.rs delete mode 100644 src/requests/all/delete_sticker_from_set.rs delete mode 100644 src/requests/all/delete_webhook.rs delete mode 100644 src/requests/all/edit_inline_message_caption.rs delete mode 100644 src/requests/all/edit_inline_message_live_location.rs delete mode 100644 src/requests/all/edit_inline_message_media.rs delete mode 100644 src/requests/all/edit_inline_message_reply_markup.rs delete mode 100644 src/requests/all/edit_inline_message_text.rs delete mode 100644 src/requests/all/edit_message_caption.rs delete mode 100644 src/requests/all/edit_message_live_location.rs delete mode 100644 src/requests/all/edit_message_media.rs delete mode 100644 src/requests/all/edit_message_reply_markup.rs delete mode 100644 src/requests/all/edit_message_text.rs delete mode 100644 src/requests/all/export_chat_invite_link.rs delete mode 100644 src/requests/all/forward_message.rs delete mode 100644 src/requests/all/get_chat.rs delete mode 100644 src/requests/all/get_chat_administrators.rs delete mode 100644 src/requests/all/get_chat_member.rs delete mode 100644 src/requests/all/get_chat_members_count.rs delete mode 100644 src/requests/all/get_file.rs delete mode 100644 src/requests/all/get_game_high_scores.rs delete mode 100644 src/requests/all/get_me.rs delete mode 100644 src/requests/all/get_my_commands.rs delete mode 100644 src/requests/all/get_sticker_set.rs delete mode 100644 src/requests/all/get_updates.rs delete mode 100644 src/requests/all/get_updates_non_strict.rs delete mode 100644 src/requests/all/get_user_profile_photos.rs delete mode 100644 src/requests/all/get_webhook_info.rs delete mode 100644 src/requests/all/kick_chat_member.rs delete mode 100644 src/requests/all/leave_chat.rs delete mode 100644 src/requests/all/mod.rs delete mode 100644 src/requests/all/pin_chat_message.rs delete mode 100644 src/requests/all/promote_chat_member.rs delete mode 100644 src/requests/all/restrict_chat_member.rs delete mode 100644 src/requests/all/send_animation.rs delete mode 100644 src/requests/all/send_audio.rs delete mode 100644 src/requests/all/send_chat_action.rs delete mode 100644 src/requests/all/send_contact.rs delete mode 100644 src/requests/all/send_dice.rs delete mode 100644 src/requests/all/send_document.rs delete mode 100644 src/requests/all/send_game.rs delete mode 100644 src/requests/all/send_invoice.rs delete mode 100644 src/requests/all/send_location.rs delete mode 100644 src/requests/all/send_media_group.rs delete mode 100644 src/requests/all/send_message.rs delete mode 100644 src/requests/all/send_photo.rs delete mode 100644 src/requests/all/send_poll.rs delete mode 100644 src/requests/all/send_sticker.rs delete mode 100644 src/requests/all/send_venue.rs delete mode 100644 src/requests/all/send_video.rs delete mode 100644 src/requests/all/send_video_note.rs delete mode 100644 src/requests/all/send_voice.rs delete mode 100644 src/requests/all/set_chat_administrator_custom_title.rs delete mode 100644 src/requests/all/set_chat_description.rs delete mode 100644 src/requests/all/set_chat_permissions.rs delete mode 100644 src/requests/all/set_chat_photo.rs delete mode 100644 src/requests/all/set_chat_sticker_set.rs delete mode 100644 src/requests/all/set_chat_title.rs delete mode 100644 src/requests/all/set_game_score.rs delete mode 100644 src/requests/all/set_my_commands.rs delete mode 100644 src/requests/all/set_sticker_position_in_set.rs delete mode 100644 src/requests/all/set_sticker_set_thumb.rs delete mode 100644 src/requests/all/set_webhook.rs delete mode 100644 src/requests/all/stop_inline_message_live_location.rs delete mode 100644 src/requests/all/stop_message_live_location.rs delete mode 100644 src/requests/all/stop_poll.rs delete mode 100644 src/requests/all/unban_chat_member.rs delete mode 100644 src/requests/all/unpin_chat_message.rs delete mode 100644 src/requests/all/upload_sticker_file.rs diff --git a/src/bot/api.rs b/src/bot/api.rs index 9facaba9..560efd01 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -1,1577 +1,14 @@ use crate::{ payloads, - requests::{ - AddStickerToSet, AnswerCallbackQuery, AnswerInlineQuery, AnswerPreCheckoutQuery, - AnswerShippingQuery, CreateNewStickerSet, DeleteChatPhoto, DeleteChatStickerSet, - DeleteMessage, DeleteStickerFromSet, DeleteWebhook, EditInlineMessageCaption, - EditInlineMessageLiveLocation, EditInlineMessageMedia, EditInlineMessageReplyMarkup, - EditInlineMessageText, EditMessageCaption, EditMessageLiveLocation, EditMessageMedia, - EditMessageReplyMarkup, EditMessageText, ExportChatInviteLink, ForwardMessage, GetChat, - GetChatAdministrators, GetChatMember, GetChatMembersCount, GetFile, GetGameHighScores, - GetMe, GetMyCommands, GetStickerSet, GetUpdates, GetUpdatesNonStrict, GetUserProfilePhotos, - GetWebhookInfo, JsonRequest, KickChatMember, LeaveChat, MultipartRequest, PinChatMessage, - PromoteChatMember, Requester, RestrictChatMember, SendAnimation, SendAudio, SendChatAction, - SendChatActionKind, SendContact, SendDice, SendDocument, SendGame, SendInvoice, - SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendPoll, SendSticker, SendVenue, - SendVideo, SendVideoNote, SendVoice, SetChatAdministratorCustomTitle, SetChatDescription, - SetChatPermissions, SetChatPhoto, SetChatStickerSet, SetChatTitle, SetGameScore, - SetMyCommands, SetStickerPositionInSet, SetStickerSetThumb, SetWebhook, - StopInlineMessageLiveLocation, StopMessageLiveLocation, StopPoll, UnbanChatMember, - UnpinChatMessage, UploadStickerFile, - }, + prelude::Requester, + requests::{JsonRequest, MultipartRequest}, types::{ BotCommand, ChatId, ChatPermissions, InlineQueryResult, InputFile, InputMedia, - InputSticker, LabeledPrice, TargetMessage, + InputSticker, LabeledPrice, }, Bot, }; -impl Bot { - /// Use this method to receive incoming updates using long polling ([wiki]). - /// - /// **Notes:** - /// 1. This method will not work if an outgoing webhook is set up. - /// 2. In order to avoid getting duplicate updates, - /// recalculate offset after each server response. - /// - /// [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.clone()) - } - - /// This is non strict version of [`get_updates`], this means that if it - /// will fail to deserialize some updates, it won't fail entirely, but - /// will just return some errors. - /// - /// Note: this is not a 'real' telegram method, this is simply - /// [`get_updates`] with changed return type. - /// - /// [`get_updates`]: crate::Bot::get_updates - pub fn get_updates_non_strict(&self) -> GetUpdatesNonStrict { - GetUpdatesNonStrict::new(self.clone()) - } - - /// Use this method to specify a url and receive incoming updates via an - /// outgoing webhook. - /// - /// Whenever there is an update for the bot, we will send an - /// HTTPS POST request to the specified url, containing a JSON-serialized - /// [`Update`]. In case of an unsuccessful request, we will give up after a - /// reasonable amount of attempts. - /// - /// If you'd like to make sure that the Webhook request comes from Telegram, - /// we recommend using a secret path in the URL, e.g. - /// `https://www.example.com/`. Since nobody else knows your bot‘s - /// token, you can be pretty sure it’s us. - /// - /// [The official docs](https://core.telegram.org/bots/api#setwebhook). - /// - /// # Params - /// - `url`: HTTPS url to send updates to. - /// - /// Use an empty string to remove webhook integration. - /// - /// [`Update`]: crate::types::Update - pub fn set_webhook(&self, url: U) -> SetWebhook - where - U: Into, - { - SetWebhook::new(self.clone(), url) - } - - /// Use this method to remove webhook integration if you decide to switch - /// back to [Bot::get_updates]. - /// - /// [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.clone()) - } - - /// Use this method to get current webhook status. - /// - /// If the bot is using [`Bot::get_updates`], will return an object with the - /// url field empty. - /// - /// [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.clone()) - } - - /// 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.clone()) - } - - /// Use this method to send text messages. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendmessage). - /// - /// # Params - /// - `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 - where - C: Into, - T: Into, - { - SendMessage::new(self.clone(), chat_id, text) - } - - /// Use this method to forward messages of any kind. - /// - /// [`The official docs`](https://core.telegram.org/bots/api#forwardmessage). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `from_chat_id`: Unique identifier for the chat where the original - /// message was sent (or channel username in the format - /// `@channelusername`). - /// - `message_id`: Message identifier in the chat specified in - /// [`from_chat_id`]. - /// - /// [`from_chat_id`]: ForwardMessage::from_chat_id - pub fn forward_message( - &self, - chat_id: C, - from_chat_id: F, - message_id: i32, - ) -> ForwardMessage - where - C: Into, - F: Into, - { - ForwardMessage::new(self.clone(), chat_id, from_chat_id, message_id) - } - - /// Use this method to send photos. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendphoto). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `photo`: Photo to send. - /// - /// Pass [`InputFile::File`] to send a photo that exists on - /// the Telegram servers (recommended), pass an [`InputFile::Url`] for - /// Telegram to get a .webp file from the Internet, or upload a new one - /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. - /// - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`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_photo(&self, chat_id: C, photo: InputFile) -> SendPhoto - where - C: Into, - { - SendPhoto::new(self.clone(), chat_id, photo) - } - - /// - /// - /// # 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 - where - C: Into, - { - SendAudio::new(self.clone(), chat_id, audio) - } - - /// Use this method to send general files. - /// - /// Bots can currently send files of any type of up to 50 MB in size, this - /// limit may be changed in the future. - /// - /// [The official docs](https://core.telegram.org/bots/api#senddocument). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `document`: File to send. - /// - /// Pass a file_id as String to send a file that exists on the - /// Telegram servers (recommended), pass an HTTP URL as a String for - /// Telegram to get a file from the Internet, or upload a new one using - /// `multipart/form-data`. [More info on Sending Files Âģ]. - /// - /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files - pub fn send_document(&self, chat_id: C, document: InputFile) -> SendDocument - where - C: Into, - { - SendDocument::new(self.clone(), chat_id, document) - } - - /// Use this method to send video files, Telegram clients support mp4 videos - /// (other formats may be sent as Document). - /// - /// Bots can currently send video files of up to 50 MB in size, this - /// limit may be changed in the future. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendvideo). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `video`: Video to sent. - /// - /// Pass [`InputFile::File`] to send a file that exists on - /// the Telegram servers (recommended), pass an [`InputFile::Url`] for - /// Telegram to get a .webp file from the Internet, or upload a new one - /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. - /// - /// [`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 - where - C: Into, - { - SendVideo::new(self.clone(), chat_id, video) - } - - /// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video - /// without sound). - /// - /// Bots can currently send animation files of up to 50 MB in size, this - /// limit may be changed in the future. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendanimation). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `animation`: Animation to send. - pub fn send_animation(&self, chat_id: C, animation: InputFile) -> SendAnimation - where - C: Into, - { - SendAnimation::new(self.clone(), chat_id, animation) - } - - /// Use this method to send audio files, if you want Telegram clients to - /// display the file as a playable voice message. - /// - /// For this to work, your audio must be in an .ogg file encoded with OPUS - /// (other formats may be sent as [`Audio`] or [`Document`]). Bots can - /// currently send voice messages of up to 50 MB in size, this limit may - /// be changed in the future. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendvoice). - /// - /// [`Audio`]: crate::types::Audio - /// [`Document`]: crate::types::Document - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `voice`: Audio file to send. - /// - /// Pass [`InputFile::File`] to send a file that exists on - /// the Telegram servers (recommended), pass an [`InputFile::Url`] for - /// Telegram to get a .webp file from the Internet, or upload a new one - /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. - /// - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`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 - where - C: Into, - { - SendVoice::new(self.clone(), chat_id, voice) - } - - /// 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. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendvideonote). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `video_note`: Video note to send. - /// - /// Pass [`InputFile::File`] to send a file that exists on the Telegram - /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a - /// .webp file from the Internet, or upload a new one using - /// [`InputFile::FileId`]. [More info on Sending Files Âģ]. - /// - /// [v.4.0]: https://telegram.org/blog/video-messages-and-telescope - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`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_video_note(&self, chat_id: C, video_note: InputFile) -> SendVideoNote - where - C: Into, - { - SendVideoNote::new(self.clone(), chat_id, video_note) - } - - /// Use this method to send a group of photos or videos as an album. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendmediagroup). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// 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 - where - C: Into, - M: Into>, - { - SendMediaGroup::new(self.clone(), chat_id, media) - } - - /// Use this method to send point on the map. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendlocation). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `latitude`: Latitude of the location. - /// - `longitude`: Latitude of the location. - pub fn send_location(&self, chat_id: C, latitude: f32, longitude: f32) -> SendLocation - where - C: Into, - { - SendLocation::new(self.clone(), chat_id, latitude, longitude) - } - - /// Use this method to edit live location messages. - /// - /// A location can be edited until its live_period expires or editing is - /// explicitly disabled by a call to stopMessageLiveLocation. On success, - /// the edited [`Message`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation). - /// - /// [`Message`]: crate::types::Message - /// - /// # Params - /// - `latitude`: Latitude of new location. - /// - `longitude`: Longitude of new location. - pub fn edit_message_live_location( - &self, - chat_id: C, - message_id: i32, - latitude: f32, - longitude: f32, - ) -> EditMessageLiveLocation - where - C: Into, - { - EditMessageLiveLocation::new(self.clone(), chat_id, message_id, latitude, longitude) - } - - /// Use this method to edit live location messages sent via the bot. - /// - /// A location can be edited until its live_period expires or editing is - /// explicitly disabled by a call to stopMessageLiveLocation. On success, - /// [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation). - /// - /// [`True`]: crate::types::True - /// - /// # Params - /// - `latitude`: Latitude of new location. - /// - `longitude`: Longitude of new location. - pub fn edit_inline_message_live_location( - &self, - inline_message_id: I, - latitude: f32, - longitude: f32, - ) -> EditInlineMessageLiveLocation - where - I: Into, - { - EditInlineMessageLiveLocation::new(self.clone(), inline_message_id, latitude, longitude) - } - - /// Use this method to stop updating a live location message before - /// `live_period` expires. - /// - /// On success, the sent [`Message`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation). - /// - /// [`Message`]: crate::types::Message - pub fn stop_message_live_location( - &self, - chat_id: C, - message_id: i32, - ) -> StopMessageLiveLocation - where - C: Into, - { - StopMessageLiveLocation::new(self.clone(), chat_id, message_id) - } - - /// Use this method to stop updating a live location message (sent via the - /// bot) before `live_period` expires. - /// - /// On success, [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation). - /// - /// [`True`]: crate::types::True - pub fn stop_inline_message_live_location( - &self, - inline_message_id: I, - ) -> StopInlineMessageLiveLocation - where - I: Into, - { - StopInlineMessageLiveLocation::new(self.clone(), inline_message_id) - } - - /// Use this method to send information about a venue. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendvenue). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `latitude`: Latitude of the venue. - /// - `longitude`: Longitude of the venue. - /// - `title`: Name of the venue. - /// - `address`: Address of the venue. - pub fn send_venue( - &self, - chat_id: C, - latitude: f32, - longitude: f32, - title: T, - address: A, - ) -> SendVenue - where - C: Into, - T: Into, - A: Into, - { - SendVenue::new(self.clone(), chat_id, latitude, longitude, title, address) - } - - /// Use this method to send phone contacts. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendcontact). - /// - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `phone_number`: Contact's phone number. - /// - `first_name`: Contact's first name. - pub fn send_contact(&self, chat_id: C, phone_number: P, first_name: F) -> SendContact - where - C: Into, - P: Into, - F: Into, - { - SendContact::new(self.clone(), chat_id, phone_number, first_name) - } - - /// Use this method to send a native poll. A native poll can't be sent to a - /// private chat. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendpoll). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `question`: Poll question, 1-255 characters. - /// - `options`: List of answer options, 2-10 strings 1-100 characters - /// each. - pub fn send_poll(&self, chat_id: C, question: Q, options: O) -> SendPoll - where - C: Into, - Q: Into, - O: Into>, - { - SendPoll::new(self.clone(), chat_id, question, options) - } - - /// Use this method when you need to tell the user that something is - /// happening on the bot's side. - /// - /// The status is set for 5 seconds or less (when a message arrives from - /// your bot, Telegram clients clear its typing status). - /// - /// ## Note - /// Example: The [ImageBot] needs some time to process a request and upload - /// the image. Instead of sending a text message along the lines of - /// “Retrieving image, please waitâ€Ļ”, the bot may use - /// [`Bot::send_chat_action`] with `action = upload_photo`. The user - /// will see a `sending photo` status for the bot. - /// - /// We only recommend using this method when a response from the bot will - /// take a **noticeable** amount of time to arrive. - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - /// [ImageBot]: https://t.me/imagebot - /// [`Bot::send_chat_action`]: crate::Bot::send_chat_action - pub fn send_chat_action(&self, chat_id: C, action: SendChatActionKind) -> SendChatAction - where - C: Into, - { - SendChatAction::new(self.clone(), chat_id, action) - } - - /// Use this method to get a list of profile pictures for a user. - /// - /// [The official docs](https://core.telegram.org/bots/api#getuserprofilephotos). - /// - /// # Params - /// - `user_id`: Unique identifier of the target user. - pub fn get_user_profile_photos(&self, user_id: i32) -> GetUserProfilePhotos { - GetUserProfilePhotos::new(self.clone(), user_id) - } - - /// Use this method to get basic info about a file and prepare it for - /// downloading. - /// - /// For the moment, bots can download files of up to `20MB` in size. - /// - /// The file can then be downloaded via the link - /// `https://api.telegram.org/file/bot/`, where `` - /// is taken from the response. 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 [`GetFile`] again. - /// - /// **Note**: This function may not preserve the original file name and MIME - /// type. You should save the file's MIME type and name (if available) when - /// the [`File`] object is received. - /// - /// [The official docs](https://core.telegram.org/bots/api#getfile). - /// - /// # Params - /// - `file_id`: File identifier to get info about. - /// - /// [`File`]: crate::types::File - /// [`GetFile`]: self::GetFile - pub fn get_file(&self, file_id: F) -> GetFile - where - F: Into, - { - GetFile::new(self.clone(), file_id) - } - - /// Use this method to kick a user from a group, a supergroup or a channel. - /// - /// In the case of supergroups and channels, the user will not be able to - /// return to the group on their own using invite links, etc., unless - /// [unbanned] first. The bot must be an administrator in the chat for - /// this to work and must have the appropriate admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#kickchatmember). - /// - /// # Params - /// - `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. - /// - /// [unbanned]: crate::Bot::unban_chat_member - pub fn kick_chat_member(&self, chat_id: C, user_id: i32) -> KickChatMember - where - C: Into, - { - KickChatMember::new(self.clone(), chat_id, user_id) - } - - /// 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, but will be able to join via link, etc. The bot must - /// be an administrator for this to work. - /// - /// [The official docs](https://core.telegram.org/bots/api#unbanchatmember). - /// - /// # Params - /// - `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 unban_chat_member(&self, chat_id: C, user_id: i32) -> UnbanChatMember - where - C: Into, - { - UnbanChatMember::new(self.clone(), chat_id, user_id) - } - - /// Use this method to restrict a user in a supergroup. - /// - /// The bot must be an administrator in the supergroup for this to work and - /// must have the appropriate admin rights. Pass `true` for all - /// permissions to lift restrictions from a user. - /// - /// [The official docs](https://core.telegram.org/bots/api#restrictchatmember). - /// - /// # Params - /// - `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. - /// - `permissions`: New user permissions. - pub fn restrict_chat_member( - &self, - chat_id: C, - user_id: i32, - permissions: ChatPermissions, - ) -> RestrictChatMember - where - C: Into, - { - RestrictChatMember::new(self.clone(), chat_id, user_id, permissions) - } - - /// Use this method to promote or demote a user in a supergroup or a - /// channel. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the appropriate admin rights. Pass False for all boolean - /// parameters to demote a user. - /// - /// [The official docs](https://core.telegram.org/bots/api#promotechatmember). - /// - /// # Params - /// - `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 promote_chat_member(&self, chat_id: C, user_id: i32) -> PromoteChatMember - where - C: Into, - { - PromoteChatMember::new(self.clone(), chat_id, user_id) - } - - /// Use this method to set default chat permissions for all members. - /// - /// The bot must be an administrator in the group or a supergroup for this - /// to work and must have the can_restrict_members admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#setchatpermissions). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `permissions`: New default chat permissions. - pub fn set_chat_permissions( - &self, - chat_id: C, - permissions: ChatPermissions, - ) -> SetChatPermissions - where - C: Into, - { - SetChatPermissions::new(self.clone(), chat_id, permissions) - } - - /// Use this method to generate a new invite link for a chat; any previously - /// generated link is revoked. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the appropriate admin rights. - /// - /// # Note - /// Each administrator in a chat generates their own invite links. Bots - /// can't use invite links generated by other administrators. If you - /// want your bot to work with invite links, it will need to generate - /// its own link using [`Bot::export_chat_invite_link`] – after this the - /// link will become available to the bot via the [`Bot::get_chat`] - /// method. If your bot needs to generate a new invite link replacing - /// its previous one, use [`Bot::export_chat_invite_link`] again. - /// - /// [The official docs](https://core.telegram.org/bots/api#exportchatinvitelink). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - /// [`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 - where - C: Into, - { - ExportChatInviteLink::new(self.clone(), chat_id) - } - - /// Use this method to set a new profile photo for the chat. - /// - /// Photos can't be changed for private chats. The bot must be an - /// administrator in the chat for this to work and must have the - /// appropriate admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#setchatphoto). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `photo`: New chat photo, uploaded using `multipart/form-data`. - pub fn set_chat_photo(&self, chat_id: C, photo: InputFile) -> SetChatPhoto - where - C: Into, - { - SetChatPhoto::new(self.clone(), chat_id, photo) - } - - /// 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 must have the appropriate admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#deletechatphoto). - /// - /// # 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 - where - C: Into, - { - DeleteChatPhoto::new(self.clone(), chat_id) - } - - /// Use this method to change the title of a chat. - /// - /// Titles can't be changed for private chats. The bot must be an - /// administrator in the chat for this to work and must have the - /// appropriate admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#setchattitle). - /// - /// # Params - /// - `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 - where - C: Into, - T: Into, - { - SetChatTitle::new(self.clone(), chat_id, title) - } - - /// Use this method to change the description of a group, a supergroup or a - /// channel. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the appropriate admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#setchatdescription). - /// - /// # 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 - where - C: Into, - { - SetChatDescription::new(self.clone(), chat_id) - } - - /// Use this method to pin a message in a group, a supergroup, or a channel. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the `can_pin_messages` admin right in the supergroup or - /// `can_edit_messages` admin right in the channel. - /// - /// [The official docs](https://core.telegram.org/bots/api#pinchatmessage). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `message_id`: Identifier of a message to pin. - pub fn pin_chat_message(&self, chat_id: C, message_id: i32) -> PinChatMessage - where - C: Into, - { - PinChatMessage::new(self.clone(), chat_id, message_id) - } - - /// Use this method to unpin a message in a group, a supergroup, or a - /// channel. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the `can_pin_messages` admin right in the supergroup or - /// `can_edit_messages` admin right in the channel. - /// - /// [The official docs](https://core.telegram.org/bots/api#unpinchatmessage). - /// - /// # 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 - where - C: Into, - { - UnpinChatMessage::new(self.clone(), chat_id) - } - - /// Use this method for your bot to leave a group, supergroup or channel. - /// - /// [The official docs](https://core.telegram.org/bots/api#leavechat). - /// - /// # 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 - where - C: Into, - { - LeaveChat::new(self.clone(), chat_id) - } - - /// 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 or channel, etc.). - /// - /// [The official docs](https://core.telegram.org/bots/api#getchat). - /// - /// # 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 - where - C: Into, - { - GetChat::new(self.clone(), chat_id) - } - - /// Use this method to get a list of administrators in a chat. - /// - /// If the chat is a group or a supergroup and no administrators were - /// appointed, only the creator will be returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#getchatadministrators). - /// - /// # 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_administrators(&self, chat_id: C) -> GetChatAdministrators - where - C: Into, - { - GetChatAdministrators::new(self.clone(), chat_id) - } - - /// Use this method to get the number of members in a chat. - /// - /// [The official docs](https://core.telegram.org/bots/api#getchatmemberscount). - /// - /// # 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 - where - C: Into, - { - GetChatMembersCount::new(self.clone(), chat_id) - } - - /// Use this method to get information about a member of a chat. - /// - /// [The official docs](https://core.telegram.org/bots/api#getchatmember). - /// - /// # Params - /// - `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 - where - C: Into, - { - GetChatMember::new(self.clone(), chat_id, user_id) - } - - /// Use this method to set a new group sticker set for a supergroup. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the appropriate admin rights. Use the field can_set_sticker_set - /// optionally returned in getChat requests to check if the bot can use - /// this method. - /// - /// [The official docs](https://core.telegram.org/bots/api#setchatstickerset). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup (in the format `@supergroupusername`). - /// - `sticker_set_name`: Name of the sticker set to be set as the group - /// sticker set. - pub fn set_chat_sticker_set(&self, chat_id: C, sticker_set_name: S) -> SetChatStickerSet - where - C: Into, - S: Into, - { - SetChatStickerSet::new(self.clone(), chat_id, sticker_set_name) - } - - /// Use this method to delete a group sticker set from a supergroup. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the appropriate admin rights. Use the field - /// `can_set_sticker_set` optionally returned in [`Bot::get_chat`] - /// requests to check if the bot can use this method. - /// - /// [The official docs](https://core.telegram.org/bots/api#deletechatstickerset). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup (in the format `@supergroupusername`). - /// - /// [`Bot::get_chat`]: crate::Bot::get_chat - pub fn delete_chat_sticker_set(&self, chat_id: C) -> DeleteChatStickerSet - where - C: Into, - { - DeleteChatStickerSet::new(self.clone(), chat_id) - } - - /// Use this method to send answers to callback queries sent from [inline - /// keyboards]. - /// - /// The answer will be displayed to the user as a notification at - /// the top of the chat screen or as an alert. - /// - /// [The official docs](https://core.telegram.org/bots/api#answercallbackquery). - /// - /// # Params - /// - `callback_query_id`: Unique identifier for the query to be answered. - /// - /// [inline keyboards]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn answer_callback_query(&self, callback_query_id: C) -> AnswerCallbackQuery - where - C: Into, - { - AnswerCallbackQuery::new(self.clone(), callback_query_id) - } - - /// Use this method to edit text and game messages. - /// - /// On success, the edited [`Message`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagetext). - /// - /// [`Message`]: crate::types::Message - /// - /// # Params - /// - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target channel (in the format `@channelusername`). - /// - `message_id`: Identifier of the message to edit. - /// - `text`: New text of the message. - pub fn edit_message_text(&self, chat_id: C, message_id: i32, text: T) -> EditMessageText - where - C: Into, - T: Into, - { - EditMessageText::new(self.clone(), chat_id, message_id, text) - } - - /// Use this method to edit text and game messages sent via the bot. - /// - /// On success, [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagetext). - /// - /// [`True`]: crate::types::True - /// - /// # Params - /// - /// - `inline_message_id`: Identifier of the inline message. - /// - `text`: New text of the message. - pub fn edit_inline_message_text( - &self, - inline_message_id: I, - text: T, - ) -> EditInlineMessageText - where - I: Into, - T: Into, - { - EditInlineMessageText::new(self.clone(), inline_message_id, text) - } - - /// Use this method to edit captions of messages sent via the bot. - /// - /// On success, [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). - /// - /// [`True`]: crate::types::True - pub fn edit_message_caption(&self, chat_id: C, message_id: i32) -> EditMessageCaption - where - C: Into, - { - EditMessageCaption::new(self.clone(), chat_id, message_id) - } - - /// Use this method to edit captions of messages sent via the bot. - /// - /// On success, [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). - /// - /// [`True`]: crate::types::True - pub fn edit_inline_message_caption(&self, inline_message_id: I) -> EditInlineMessageCaption - where - I: Into, - { - EditInlineMessageCaption::new(self.clone(), inline_message_id) - } - - /// Use this method to edit animation, audio, document, photo, or video - /// messages. - /// - /// If a message is a part of a message album, then it can be edited only to - /// a photo or a video. Otherwise, message type can be changed - /// arbitrarily. On success, the edited [`Message`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagemedia). - /// - /// [`Message`]: crate::types::Message - pub fn edit_message_media( - &self, - chat_id: C, - message_id: i32, - media: InputMedia, - ) -> EditMessageMedia - where - C: Into, - { - EditMessageMedia::new(self.clone(), chat_id, message_id, media) - } - - /// Use this method to edit animation, audio, document, photo, or video - /// messages sent via the bot. - /// - /// If a message is a part of a message album, then it can be edited only to - /// a photo or a video. Otherwise, message type can be changed - /// arbitrarily. When this method is used, new file can't be uploaded. - /// Use previously uploaded file via its `file_id` or specify a URL. On - /// success, [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagemedia). - /// - /// [`True`]: crate::types::True - pub fn edit_inline_message_media( - &self, - inline_message_id: I, - media: InputMedia, - ) -> EditInlineMessageMedia - where - I: Into, - { - EditInlineMessageMedia::new(self.clone(), inline_message_id, media) - } - - /// Use this method to edit only the reply markup of messages. - /// - /// On success, the edited [`Message`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup). - /// - /// [`Message`]: crate::types::Message - pub fn edit_message_reply_markup( - &self, - chat_id: C, - message_id: i32, - ) -> EditMessageReplyMarkup - where - C: Into, - { - EditMessageReplyMarkup::new(self.clone(), chat_id, message_id) - } - - /// Use this method to edit only the reply markup of messages sent via the - /// bot. - /// - /// On success, [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup). - /// - /// [`Message`]: crate::types::Message - /// [`True`]: crate::types::True - pub fn edit_inline_message_reply_markup( - &self, - inline_message_id: I, - ) -> EditInlineMessageReplyMarkup - where - I: Into, - { - EditInlineMessageReplyMarkup::new(self.clone(), inline_message_id) - } - - /// Use this method to stop a poll which was sent by the bot. - /// - /// [The official docs](https://core.telegram.org/bots/api#stoppoll). - /// - /// - /// # Params - /// - `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 - where - C: Into, - { - StopPoll::new(self.clone(), chat_id, message_id) - } - - /// Use this method to delete a message, including service messages. - /// - /// The limitations are: - /// - A message can only be deleted if it was sent less than 48 hours ago. - /// - Bots can delete outgoing messages in private chats, groups, and - /// supergroups. - /// - Bots can delete incoming messages in private chats. - /// - Bots granted can_post_messages permissions can delete outgoing - /// messages in channels. - /// - If the bot is an administrator of a group, it can delete any message - /// there. - /// - If the bot has can_delete_messages permission in a supergroup or a - /// channel, it can delete any message there. - /// - /// [The official docs](https://core.telegram.org/bots/api#deletemessage). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target channel (in the format `@channelusername`). - /// - `message_id`: Identifier of the message to delete. - pub fn delete_message(&self, chat_id: C, message_id: i32) -> DeleteMessage - where - C: Into, - { - DeleteMessage::new(self.clone(), chat_id, message_id) - } - - /// Use this method to send static .WEBP or [animated] .TGS stickers. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendsticker). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target channel (in the format `@channelusername`). - /// - `sticker`: Sticker to send. - /// - /// Pass [`InputFile::File`] to send a file that exists on the Telegram - /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a - /// .webp file from the Internet, or upload a new one using - /// [`InputFile::FileId`]. [More info on Sending Files Âģ]. - /// - /// [animated]: https://telegram.org/blog/animated-stickers - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`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 - where - C: Into, - { - SendSticker::new(self.clone(), chat_id, sticker) - } - - /// Use this method to get a sticker set. - /// - /// [The official docs](https://core.telegram.org/bots/api#getstickerset). - /// - /// # Params - /// - `name`: Name of the sticker set. - pub fn get_sticker_set(&self, name: N) -> GetStickerSet - where - N: Into, - { - GetStickerSet::new(self.clone(), name) - } - - /// 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 be used multiple times). - /// - /// [The official docs](https://core.telegram.org/bots/api#uploadstickerfile). - /// - /// # Params - /// - `user_id`: User identifier of sticker file owner. - /// - `png_sticker`: **Png** image with the sticker, must be up to 512 - /// kilobytes in size, dimensions must not exceed 512px, and either - /// width or height must be exactly 512px. [More info on Sending Files - /// Âģ]. - /// - /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files - /// [`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, user_id: i32, png_sticker: InputFile) -> UploadStickerFile { - UploadStickerFile::new(self.clone(), user_id, png_sticker) - } - - /// 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). - /// - /// # Params - /// - `user_id`: User identifier of created sticker set owner. - /// - `name`: Short name of sticker set, to be used in `t.me/addstickers/` - /// URLs (e.g., animals). Can contain only english letters, digits and - /// underscores. - /// - /// Must begin with a letter, can't contain consecutive underscores and must - /// end in `_by_`. `` is case insensitive. 1-64 - /// characters. - /// - `title`: Sticker set title, 1-64 characters. - pub fn create_new_sticker_set( - &self, - user_id: i32, - name: N, - title: T, - sticker_type: InputSticker, - emojis: E, - ) -> CreateNewStickerSet - where - N: Into, - T: Into, - E: Into, - { - CreateNewStickerSet::new(self.clone(), user_id, name, title, sticker_type, emojis) - } - - /// 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). - /// - /// # Params - /// - `user_id`: User identifier of sticker set owner. - /// - `name`: Sticker set name. - /// - `emojis`: One or more emoji corresponding to the sticker. - pub fn add_sticker_to_set( - &self, - user_id: i32, - name: N, - sticker_type: InputSticker, - emojis: E, - ) -> AddStickerToSet - where - N: Into, - E: Into, - { - AddStickerToSet::new(self.clone(), user_id, name, sticker_type, emojis) - } - - /// 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). - /// - /// # Params - /// - `sticker`: File identifier of the sticker. - /// - `position`: New sticker position in the set, zero-based. - pub fn set_sticker_position_in_set( - &self, - sticker: S, - position: i32, - ) -> SetStickerPositionInSet - where - S: Into, - { - SetStickerPositionInSet::new(self.clone(), sticker, position) - } - - /// Use this method to delete a sticker from a set created by the bot. - /// - /// [The official docs](https://core.telegram.org/bots/api#deletestickerfromset). - /// - /// # Params - /// - `sticker`: File identifier of the sticker. - pub fn delete_sticker_from_set(&self, sticker: S) -> DeleteStickerFromSet - where - S: Into, - { - DeleteStickerFromSet::new(self.clone(), sticker) - } - - /// Use this method to send answers to an inline query. - /// - /// No more than **50** results per query are allowed. - /// - /// [The official docs](https://core.telegram.org/bots/api#answerinlinequery). - /// - /// # Params - /// - `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, inline_query_id: I, results: R) -> AnswerInlineQuery - where - I: Into, - R: Into>, - { - AnswerInlineQuery::new(self.clone(), inline_query_id, results) - } - - /// Use this method to send invoices. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendinvoice). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target private chat. - /// - `title`: Product name, 1-32 characters. - /// - `description`: Product description, 1-255 characters. - /// - `payload`: Bot-defined invoice payload, 1-128 bytes. This will not - /// be displayed to the user, use for your internal processes. - /// - `provider_token`: Payments provider token, obtained via - /// [@Botfather]. - /// - `start_parameter`: Unique deep-linking parameter that can be used to - /// generate this invoice when used as a start parameter. - /// - `currency`: Three-letter ISO 4217 currency code, see [more on - /// currencies]. - /// - `prices`: Price breakdown, a list of components (e.g. product price, - /// tax, discount, delivery cost, delivery tax, bonus, etc.). - /// - /// [more on currencies]: https://core.telegram.org/bots/payments#supported-currencies - /// [@Botfather]: https://t.me/botfather - #[allow(clippy::too_many_arguments)] - pub fn send_invoice( - &self, - chat_id: i32, - title: T, - description: D, - payload: Pl, - provider_token: Pt, - start_parameter: S, - currency: C, - prices: Pr, - ) -> SendInvoice - where - T: Into, - D: Into, - Pl: Into, - Pt: Into, - S: Into, - C: Into, - Pr: Into>, - { - SendInvoice::new( - self.clone(), - chat_id, - title, - description, - payload, - provider_token, - start_parameter, - currency, - prices, - ) - } - - /// 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 `pre_checkout_query`. Use this method to respond to such - /// pre-checkout queries. Note: The Bot API must receive an answer - /// within 10 seconds after the pre-checkout query was sent. - /// - /// [The official docs](https://core.telegram.org/bots/api#answerprecheckoutquery). - /// - /// # Params - /// - `shipping_query_id`: Unique identifier for the query to be answered. - /// - `ok`: Specify `true` if delivery to the specified address is - /// possible and `false` if there are any problems (for example, if - /// delivery to the specified address is not possible). - /// - /// [`Update`]: crate::types::Update - pub fn answer_shipping_query(&self, shipping_query_id: S, ok: bool) -> AnswerShippingQuery - where - S: Into, - { - AnswerShippingQuery::new(self.clone(), shipping_query_id, ok) - } - - /// 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 `pre_checkout_query`. Use this method to respond to such - /// pre-checkout queries. Note: The Bot API must receive an answer - /// within 10 seconds after the pre-checkout query was sent. - /// - /// [The official docs](https://core.telegram.org/bots/api#answerprecheckoutquery). - /// - /// # Params - /// - `pre_checkout_query_id`: Unique identifier for the query to be - /// answered. - /// - `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, - pre_checkout_query_id: P, - ok: bool, - ) -> AnswerPreCheckoutQuery - where - P: Into, - { - AnswerPreCheckoutQuery::new(self.clone(), pre_checkout_query_id, ok) - } - - /// Use this method to send a game. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendgame). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat. - /// - `game_short_name`: Short name of the game, serves as the unique - /// 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 - where - G: Into, - { - SendGame::new(self.clone(), chat_id, game_short_name) - } - - /// Use this method to set the score of the specified user in a game. - /// - /// On success, if the message was sent by the bot, returns the edited - /// [`Message`], otherwise returns [`True`]. Returns an error, if the new - /// score is not greater than the user's current score in the chat and - /// force is `false`. - /// - /// [The official docs](https://core.telegram.org/bots/api#setgamescore). - /// - /// # Params - /// - `target`: Target message, either chat id and message id or inline - /// message id. - /// - `user_id`: User identifier. - /// - `score`: New score, must be non-negative. - /// - /// [`Message`]: crate::types::Message - /// [`True`]: crate::types::True - pub fn set_game_score(&self, target: T, user_id: i32, score: i32) -> SetGameScore - where - T: Into, - { - SetGameScore::new(self.clone(), target, user_id, score) - } - - /// Use this method to get data for high score tables. - /// - /// Will return the score of the specified user and several of his neighbors - /// in a game. - /// - /// # Note - /// This method will currently return scores for the target user, plus two - /// of his closest neighbors on each side. Will also return the top - /// three users if the user and his neighbors are not among them. Please - /// note that this behavior is subject to change. - /// - /// [The official docs](https://core.telegram.org/bots/api#getgamehighscores). - /// - /// # Params - /// - `target`: Target message, either chat id and message id or inline - /// message id. - /// - `user_id`: Target user id. - pub fn get_game_high_scores(&self, target: T, user_id: i32) -> GetGameHighScores - where - T: Into, - { - GetGameHighScores::new(self.clone(), target, user_id) - } - - /// 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). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target channel (in the format `@channelusername`). - /// - `user_id`: Unique identifier of the target user. - /// - `custom_title`: New custom title for the administrator; 0-16 - /// characters, emoji are not allowed. - pub fn set_chat_administrator_custom_title( - &self, - chat_id: C, - user_id: i32, - custom_title: CT, - ) -> SetChatAdministratorCustomTitle - where - C: Into, - CT: Into, - { - SetChatAdministratorCustomTitle::new(self.clone(), chat_id, user_id, custom_title) - } - - /// Use this method to send an animated emoji that will display a random - /// value. - /// - /// [The official docs](https://core.telegram.org/bots/api#senddice). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target channel (in the format `@channelusername`). - pub fn send_dice(&self, chat_id: C) -> SendDice - where - C: Into, - { - SendDice::new(self.clone(), chat_id) - } - - /// Use this method to get the current list of the bot's commands. - /// - /// [The official docs](https://core.telegram.org/bots/api#getmycommands). - pub fn get_my_commands(&self) -> GetMyCommands { - GetMyCommands::new(self.clone()) - } - - /// Use this method to change the list of the bot's commands. - /// - /// [The official docs](https://core.telegram.org/bots/api#setmycommands). - /// - /// # Params - /// - `commands`: A JSON-serialized list of bot commands to be set as the - /// list of the bot's commands. At most 100 commands can be specified. - pub fn set_my_commands(&self, commands: C) -> SetMyCommands - where - C: Into>, - { - SetMyCommands::new(self.clone(), commands) - } - - /// Use this method to set the thumbnail of a sticker set. Animated - /// thumbnails can be set for animated sticker sets only. - /// - /// [The official docs](https://core.telegram.org/bots/api#setstickersetthumb). - /// - /// # Params - /// - `name`: Sticker set name. - /// - `user_id`: User identifier of the sticker set owner. - pub fn set_sticker_set_thumb(&self, name: S, user_id: i32) -> SetStickerSetThumb - where - S: Into, - { - SetStickerSetThumb::new(self.clone(), name, user_id) - } -} - impl Requester for Bot { type Err = crate::errors::RequestError; diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 6187cf41..8fb01095 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -129,7 +129,7 @@ impl Bot { .expect("serialization of request to be infallible"); // async move to capture client&token&api_url¶ms - async move { net::request_json2(&client, token.as_ref(), api_url.get(), P::NAME, params).await } + async move { net::request_json(&client, token.as_ref(), api_url.get(), P::NAME, params).await } } pub(crate) fn execute_multipart

( @@ -149,7 +149,7 @@ impl Bot { // async move to capture client&token&api_url¶ms async move { let params = params.await?; - net::request_multipart2(&client, token.as_ref(), api_url.get(), P::NAME, params).await + net::request_multipart(&client, token.as_ref(), api_url.get(), P::NAME, params).await } } } diff --git a/src/net/mod.rs b/src/net/mod.rs index 88f857cb..a6699ca5 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -1,6 +1,6 @@ pub(crate) use self::{ download::{download_file, download_file_stream}, - request::{request_json, request_json2, request_multipart, request_multipart2}, + request::{request_json, request_multipart}, telegram_response::TelegramResponse, }; diff --git a/src/net/request.rs b/src/net/request.rs index 887199e5..15fb3d27 100644 --- a/src/net/request.rs +++ b/src/net/request.rs @@ -4,80 +4,13 @@ use reqwest::{ header::{HeaderValue, CONTENT_TYPE}, Client, Response, }; -use serde::{de::DeserializeOwned, Serialize}; +use serde::de::DeserializeOwned; -use crate::{ - net::{TelegramResponse, TELEGRAM_API_URL}, - requests::ResponseResult, - serde_multipart::to_form, - RequestError, -}; +use crate::{net::TelegramResponse, requests::ResponseResult, RequestError}; const DELAY_ON_SERVER_ERROR: Duration = Duration::from_secs(10); -pub async fn request_multipart( - client: &Client, - token: &str, - method_name: &str, - params: &P, // I'll regret this -) -> ResponseResult -where - P: Serialize, - R: DeserializeOwned, -{ - use crate::serde_multipart::Error; - let form = match to_form(params).await { - Ok(x) => x, - Err(Error::Io(ioerr)) => return Err(RequestError::Io(ioerr)), - Err(_) => unreachable!( - "we don't create requests those fail to serialize (if you see this, open an issue :|)" - ), - }; - - let response = client - .post(crate::net::method_url( - reqwest::Url::parse(TELEGRAM_API_URL).expect("failed to parse default url"), - token, - method_name, - )) - .multipart(form) - .send() - .await - .map_err(RequestError::NetworkError)?; - - process_response(response).await -} - -pub async fn request_json( - client: &Client, - token: &str, - method_name: &str, - params: &P, -) -> ResponseResult -where - P: Serialize, - R: DeserializeOwned, -{ - let response = client - .post(crate::net::method_url( - reqwest::Url::parse(TELEGRAM_API_URL).expect("failed to parse default url"), - token, - method_name, - )) - .json(params) - .send() - .await - .map_err(RequestError::NetworkError)?; - - process_response(response).await -} - -// FIXME(waffle): -// request_{json,mutipart} are currently used in old code, so we keep them -// for now when they will not be used anymore, we should remove them -// and rename request_{json,mutipart}2 => request_{json,mutipart} - -pub async fn request_multipart2( +pub async fn request_multipart( client: &Client, token: &str, api_url: reqwest::Url, @@ -97,7 +30,7 @@ where process_response(response).await } -pub async fn request_json2( +pub async fn request_json( client: &Client, token: &str, api_url: reqwest::Url, diff --git a/src/requests/all/add_sticker_to_set.rs b/src/requests/all/add_sticker_to_set.rs deleted file mode 100644 index a881e17e..00000000 --- a/src/requests/all/add_sticker_to_set.rs +++ /dev/null @@ -1,96 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - types::{MaskPosition, True}, - Bot, -}; - -use crate::{ - requests::{RequestOld, ResponseResult}, - types::InputSticker, -}; - -/// 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). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct AddStickerToSet { - #[serde(skip_serializing)] - bot: Bot, - pub user_id: i32, - pub name: String, - #[serde(flatten)] - pub sticker_type: InputSticker, - pub emojis: String, - pub mask_position: Option, -} - -#[async_trait::async_trait] -impl RequestOld for AddStickerToSet { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_multipart(self.bot.client(), self.bot.token(), "addStickerToSet", self).await - } -} - -impl AddStickerToSet { - pub(crate) fn new( - bot: Bot, - user_id: i32, - name: N, - sticker_type: InputSticker, - emojis: E, - ) -> Self - where - N: Into, - E: Into, - { - Self { - bot, - user_id, - name: name.into(), - sticker_type, - emojis: emojis.into(), - mask_position: None, - } - } - - /// User identifier of sticker set owner. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// Sticker set name. - pub fn name(mut self, val: T) -> Self - where - T: Into, - { - self.name = val.into(); - self - } - - pub fn sticker_type(mut self, val: InputSticker) -> Self { - self.sticker_type = val; - self - } - - /// One or more emoji corresponding to the sticker. - pub fn emojis(mut self, val: T) -> Self - where - T: Into, - { - self.emojis = val.into(); - self - } - - /// A JSON-serialized object for position where the mask should be placed on - /// faces. - pub fn mask_position(mut self, val: MaskPosition) -> Self { - self.mask_position = Some(val); - self - } -} diff --git a/src/requests/all/answer_callback_query.rs b/src/requests/all/answer_callback_query.rs deleted file mode 100644 index 34aef9ba..00000000 --- a/src/requests/all/answer_callback_query.rs +++ /dev/null @@ -1,101 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::True, - Bot, -}; - -/// Use this method to send answers to callback queries sent from [inline -/// keyboards]. -/// -/// The answer will be displayed to the user as a notification at -/// the top of the chat screen or as an alert. -/// -/// [The official docs](https://core.telegram.org/bots/api#answercallbackquery). -/// -/// [inline keyboards]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct AnswerCallbackQuery { - #[serde(skip_serializing)] - bot: Bot, - pub callback_query_id: String, - pub text: Option, - pub show_alert: Option, - pub url: Option, - pub cache_time: Option, -} - -#[async_trait::async_trait] -impl RequestOld for AnswerCallbackQuery { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "answerCallbackQuery", &self).await - } -} - -impl AnswerCallbackQuery { - pub(crate) fn new(bot: Bot, callback_query_id: C) -> Self - where - C: Into, - { - let callback_query_id = callback_query_id.into(); - Self { bot, callback_query_id, text: None, show_alert: None, url: None, cache_time: None } - } - - /// Unique identifier for the query to be answered. - pub fn callback_query_id(mut self, val: T) -> Self - where - T: Into, - { - self.callback_query_id = val.into(); - self - } - - /// Text of the notification. If not specified, nothing will be shown to the - /// user, 0-200 characters. - pub fn text(mut self, val: T) -> Self - where - T: Into, - { - self.text = Some(val.into()); - self - } - - /// If `true`, an alert will be shown by the client instead of a - /// notification at the top of the chat screen. Defaults to `false`. - pub fn show_alert(mut self, val: bool) -> Self { - self.show_alert = Some(val); - self - } - - /// URL that will be opened by the user's client. If you have created a - /// [`Game`] and accepted the conditions via [@Botfather], specify the - /// URL that opens your game – note that this will only work if the - /// query comes from a [`callback_game`] button. - /// - /// Otherwise, you may use links like `t.me/your_bot?start=XXXX` that open - /// your bot with a parameter. - /// - /// [@Botfather]: https://t.me/botfather - /// [`callback_game`]: crate::types::InlineKeyboardButton - /// [`Game`]: crate::types::Game - pub fn url(mut self, val: T) -> Self - where - T: Into, - { - self.url = Some(val.into()); - self - } - - /// The maximum amount of time in seconds that the result of the callback - /// query may be cached client-side. Telegram apps will support caching - /// starting in version 3.14. Defaults to 0. - pub fn cache_time(mut self, val: i32) -> Self { - self.cache_time = Some(val); - self - } -} diff --git a/src/requests/all/answer_inline_query.rs b/src/requests/all/answer_inline_query.rs deleted file mode 100644 index 1030d38f..00000000 --- a/src/requests/all/answer_inline_query.rs +++ /dev/null @@ -1,146 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{InlineQueryResult, True}, - Bot, -}; - -/// Use this method to send answers to an inline query. -/// -/// No more than **50** results per query are allowed. -/// -/// [The official docs](https://core.telegram.org/bots/api#answerinlinequery). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct AnswerInlineQuery { - #[serde(skip_serializing)] - bot: Bot, - pub inline_query_id: String, - pub results: Vec, - pub cache_time: Option, - pub is_personal: Option, - pub next_offset: Option, - pub switch_pm_text: Option, - pub switch_pm_parameter: Option, -} - -#[async_trait::async_trait] -impl RequestOld for AnswerInlineQuery { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "answerInlineQuery", &self).await - } -} - -impl AnswerInlineQuery { - pub(crate) fn new(bot: Bot, inline_query_id: I, results: R) -> Self - where - I: Into, - R: Into>, - { - let inline_query_id = inline_query_id.into(); - let results = results.into(); - Self { - bot, - inline_query_id, - results, - cache_time: None, - is_personal: None, - next_offset: None, - switch_pm_text: None, - switch_pm_parameter: None, - } - } - - /// Unique identifier for the answered query. - pub fn inline_query_id(mut self, val: T) -> Self - where - T: Into, - { - self.inline_query_id = val.into(); - self - } - - /// A JSON-serialized array of results for the inline query. - pub fn results(mut self, val: T) -> Self - where - T: Into>, - { - self.results = val.into(); - self - } - - /// The maximum amount of time in seconds that the result of the inline - /// query may be cached on the server. - /// - /// Defaults to 300. - pub fn cache_time(mut self, val: i32) -> Self { - self.cache_time = Some(val); - self - } - - /// Pass `true`, if results may be cached on the server side only for the - /// user that sent the query. - /// - /// By default, results may be returned to any user who sends the same - /// query. - #[allow(clippy::wrong_self_convention)] - pub fn is_personal(mut self, val: bool) -> Self { - self.is_personal = Some(val); - self - } - - /// Pass the offset that a client should send in the next query with the - /// same text to receive more results. - /// - /// Pass an empty string if there are no more results or if you don‘t - /// support pagination. Offset length can’t exceed 64 bytes. - pub fn next_offset(mut self, val: T) -> Self - where - T: Into, - { - self.next_offset = Some(val.into()); - self - } - - /// If passed, clients will display a button with specified text that - /// switches the user to a private chat with the bot and sends the bot a - /// start message with the parameter [`switch_pm_parameter`]. - /// - /// [`switch_pm_parameter`]: - /// crate::requests::AnswerInlineQuery::switch_pm_parameter - pub fn switch_pm_text(mut self, val: T) -> Self - where - T: Into, - { - self.switch_pm_text = Some(val.into()); - self - } - - /// [Deep-linking] parameter for the /start message sent to the bot when - /// user presses the switch button. 1-64 characters, only `A-Z`, `a-z`, - /// `0-9`, `_` and `-` are allowed. - /// - /// Example: An inline bot that sends YouTube videos can ask the user to - /// connect the bot to their YouTube account to adapt search results - /// accordingly. To do this, it displays a ‘Connect your YouTube account’ - /// button above the results, or even before showing any. The user presses - /// the button, switches to a private chat with the bot and, in doing so, - /// passes a start parameter that instructs the bot to return an oauth link. - /// Once done, the bot can offer a [`switch_inline`] button so that the user - /// can easily return to the chat where they wanted to use the bot's - /// inline capabilities. - /// - /// [Deep-linking]: https://core.telegram.org/bots#deep-linking - /// [`switch_inline`]: crate::types::InlineKeyboardMarkup - pub fn switch_pm_parameter(mut self, val: T) -> Self - where - T: Into, - { - self.switch_pm_parameter = Some(val.into()); - self - } -} diff --git a/src/requests/all/answer_pre_checkout_query.rs b/src/requests/all/answer_pre_checkout_query.rs deleted file mode 100644 index c00c5c11..00000000 --- a/src/requests/all/answer_pre_checkout_query.rs +++ /dev/null @@ -1,82 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::True, - Bot, -}; - -/// 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 -/// `pre_checkout_query`. Use this method to respond to such pre-checkout -/// queries. -/// -/// # Note -/// The Bot API must receive an answer within 10 seconds after the pre-checkout -/// query was sent. -/// -/// [The official docs](https://core.telegram.org/bots/api#answerprecheckoutquery). -/// -/// [`Update`]: crate::types::Update -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct AnswerPreCheckoutQuery { - #[serde(skip_serializing)] - bot: Bot, - pub pre_checkout_query_id: String, - pub ok: bool, - pub error_message: Option, -} - -#[async_trait::async_trait] -impl RequestOld for AnswerPreCheckoutQuery { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "answerPreCheckoutQuery", &self) - .await - } -} - -impl AnswerPreCheckoutQuery { - pub(crate) fn new

(bot: Bot, pre_checkout_query_id: P, ok: bool) -> Self - where - P: Into, - { - let pre_checkout_query_id = pre_checkout_query_id.into(); - Self { bot, pre_checkout_query_id, ok, error_message: None } - } - - /// Unique identifier for the query to be answered. - pub fn pre_checkout_query_id(mut self, val: T) -> Self - where - T: Into, - { - self.pre_checkout_query_id = val.into(); - self - } - - /// 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. - pub fn ok(mut self, val: bool) -> Self { - self.ok = val; - self - } - - /// Required if ok is `false`. Error message in human readable form that - /// explains the reason for failure to proceed with the checkout (e.g. - /// "Sorry, somebody just bought the last of our amazing black T-shirts - /// while you were busy filling out your payment details. Please choose a - /// different color or garment!"). - /// - /// Telegram will display this message to the user. - pub fn error_message(mut self, val: T) -> Self - where - T: Into, - { - self.error_message = Some(val.into()); - self - } -} diff --git a/src/requests/all/answer_shipping_query.rs b/src/requests/all/answer_shipping_query.rs deleted file mode 100644 index bb66facf..00000000 --- a/src/requests/all/answer_shipping_query.rs +++ /dev/null @@ -1,86 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ShippingOption, True}, - Bot, -}; - -/// 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 -/// queries. -/// -/// [The official docs](https://core.telegram.org/bots/api#answershippingquery). -/// -/// [`Update`]: crate::types::Update -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct AnswerShippingQuery { - #[serde(skip_serializing)] - bot: Bot, - pub shipping_query_id: String, - pub ok: bool, - pub shipping_options: Option>, - pub error_message: Option, -} - -#[async_trait::async_trait] -impl RequestOld for AnswerShippingQuery { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "answerShippingQuery", &self).await - } -} - -impl AnswerShippingQuery { - pub(crate) fn new(bot: Bot, shipping_query_id: S, ok: bool) -> Self - where - S: Into, - { - let shipping_query_id = shipping_query_id.into(); - Self { bot, shipping_query_id, ok, shipping_options: None, error_message: None } - } - - /// Unique identifier for the query to be answered. - pub fn shipping_query_id(mut self, val: T) -> Self - where - T: Into, - { - self.shipping_query_id = val.into(); - self - } - - /// Specify `true` if delivery to the specified address is possible and - /// `false` if there are any problems (for example, if delivery to the - /// specified address is not possible). - pub fn ok(mut self, val: bool) -> Self { - self.ok = val; - self - } - - /// Required if ok is `true`. A JSON-serialized array of available shipping - /// options. - pub fn shipping_options(mut self, val: T) -> Self - where - T: Into>, - { - self.shipping_options = Some(val.into()); - self - } - - /// Required if ok is `false`. Error message in human readable form that - /// explains why it is impossible to complete the order (e.g. "Sorry, - /// delivery to your desired address is unavailable'). - /// - /// Telegram will display this message to the user. - pub fn error_message(mut self, val: T) -> Self - where - T: Into, - { - self.error_message = Some(val.into()); - self - } -} diff --git a/src/requests/all/create_new_sticker_set.rs b/src/requests/all/create_new_sticker_set.rs deleted file mode 100644 index 4d781bee..00000000 --- a/src/requests/all/create_new_sticker_set.rs +++ /dev/null @@ -1,120 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{InputSticker, MaskPosition, True}, - Bot, -}; - -/// 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). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct CreateNewStickerSet { - #[serde(skip_serializing)] - bot: Bot, - pub user_id: i32, - pub name: String, - pub title: String, - #[serde(flatten)] - pub sticker: InputSticker, - pub emojis: String, - pub contains_masks: Option, - pub mask_position: Option, -} - -#[async_trait::async_trait] -impl RequestOld for CreateNewStickerSet { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_multipart(self.bot.client(), self.bot.token(), "createNewStickerSet", self) - .await - } -} - -impl CreateNewStickerSet { - pub(crate) fn new( - bot: Bot, - user_id: i32, - name: N, - title: T, - sticker_type: InputSticker, - emojis: E, - ) -> Self - where - N: Into, - T: Into, - E: Into, - { - Self { - bot, - user_id, - name: name.into(), - title: title.into(), - sticker: sticker_type, - emojis: emojis.into(), - contains_masks: None, - mask_position: None, - } - } - - /// User identifier of created sticker set owner. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// Short name of sticker set, to be used in `t.me/addstickers/` URLs (e.g., - /// animals). Can contain only english letters, digits and underscores. - /// - /// Must begin with a letter, can't contain consecutive underscores and must - /// end in `_by_`. `` is case insensitive. - /// 1-64 characters. - pub fn name(mut self, val: T) -> Self - where - T: Into, - { - self.name = val.into(); - self - } - - /// Sticker set title, 1-64 characters. - pub fn title(mut self, val: T) -> Self - where - T: Into, - { - self.title = val.into(); - self - } - - pub fn sticker_type(mut self, val: InputSticker) -> Self { - self.sticker = val; - self - } - - /// One or more emoji corresponding to the sticker. - pub fn emojis(mut self, val: T) -> Self - where - T: Into, - { - self.emojis = val.into(); - self - } - - /// Pass `true`, if a set of mask stickers should be created. - pub fn contains_masks(mut self, val: bool) -> Self { - self.contains_masks = Some(val); - self - } - - /// A JSON-serialized object for position where the mask should be placed on - /// faces. - pub fn mask_position(mut self, val: MaskPosition) -> Self { - self.mask_position = Some(val); - self - } -} diff --git a/src/requests/all/delete_chat_photo.rs b/src/requests/all/delete_chat_photo.rs deleted file mode 100644 index f915b879..00000000 --- a/src/requests/all/delete_chat_photo.rs +++ /dev/null @@ -1,50 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// 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 -/// must have the appropriate admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#deletechatphoto). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct DeleteChatPhoto { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, -} - -#[async_trait::async_trait] -impl RequestOld for DeleteChatPhoto { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "deleteChatPhoto", &self).await - } -} - -impl DeleteChatPhoto { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/delete_chat_sticker_set.rs b/src/requests/all/delete_chat_sticker_set.rs deleted file mode 100644 index b0ee8542..00000000 --- a/src/requests/all/delete_chat_sticker_set.rs +++ /dev/null @@ -1,55 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to delete a group sticker set from a supergroup. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. Use the field `can_set_sticker_set` optionally -/// returned in [`Bot::get_chat`] requests to check if the bot can use this -/// method. -/// -/// [The official docs](https://core.telegram.org/bots/api#deletechatstickerset). -/// -/// [`Bot::get_chat`]: crate::Bot::get_chat -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct DeleteChatStickerSet { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, -} - -#[async_trait::async_trait] -impl RequestOld for DeleteChatStickerSet { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "deleteChatStickerSet", &self).await - } -} - -impl DeleteChatStickerSet { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup (in the format `@supergroupusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/delete_message.rs b/src/requests/all/delete_message.rs deleted file mode 100644 index c47d9380..00000000 --- a/src/requests/all/delete_message.rs +++ /dev/null @@ -1,67 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to delete a message, including service messages. -/// -/// The limitations are: -/// - A message can only be deleted if it was sent less than 48 hours ago. -/// - Bots can delete outgoing messages in private chats, groups, and -/// supergroups. -/// - Bots can delete incoming messages in private chats. -/// - Bots granted can_post_messages permissions can delete outgoing messages -/// in channels. -/// - If the bot is an administrator of a group, it can delete any message -/// there. -/// - If the bot has can_delete_messages permission in a supergroup or a -/// channel, it can delete any message there. -/// -/// [The official docs](https://core.telegram.org/bots/api#deletemessage). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct DeleteMessage { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub message_id: i32, -} - -#[async_trait::async_trait] -impl RequestOld for DeleteMessage { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "deleteMessage", &self).await - } -} - -impl DeleteMessage { - pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of the message to delete. - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } -} diff --git a/src/requests/all/delete_sticker_from_set.rs b/src/requests/all/delete_sticker_from_set.rs deleted file mode 100644 index e17a1cf1..00000000 --- a/src/requests/all/delete_sticker_from_set.rs +++ /dev/null @@ -1,47 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::True, - Bot, -}; - -/// 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(Debug, Clone, Serialize)] -pub struct DeleteStickerFromSet { - #[serde(skip_serializing)] - bot: Bot, - pub sticker: String, -} - -#[async_trait::async_trait] -impl RequestOld for DeleteStickerFromSet { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "deleteStickerFromSet", &self).await - } -} - -impl DeleteStickerFromSet { - pub(crate) fn new(bot: Bot, sticker: S) -> Self - where - S: Into, - { - let sticker = sticker.into(); - Self { bot, sticker } - } - - /// File identifier of the sticker. - pub fn sticker(mut self, val: T) -> Self - where - T: Into, - { - self.sticker = val.into(); - self - } -} diff --git a/src/requests/all/delete_webhook.rs b/src/requests/all/delete_webhook.rs deleted file mode 100644 index 110cbe38..00000000 --- a/src/requests/all/delete_webhook.rs +++ /dev/null @@ -1,37 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::True, - Bot, -}; - -/// Use this method to remove webhook integration if you decide to switch back -/// to [Bot::get_updates]. -/// -/// [The official docs](https://core.telegram.org/bots/api#deletewebhook). -/// -/// [Bot::get_updates]: crate::Bot::get_updates -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct DeleteWebhook { - #[serde(skip_serializing)] - bot: Bot, -} - -#[async_trait::async_trait] -impl RequestOld for DeleteWebhook { - type Output = True; - - #[allow(clippy::trivially_copy_pass_by_ref)] - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "deleteWebhook", &self).await - } -} - -impl DeleteWebhook { - pub(crate) fn new(bot: Bot) -> Self { - Self { bot } - } -} diff --git a/src/requests/all/edit_inline_message_caption.rs b/src/requests/all/edit_inline_message_caption.rs deleted file mode 100644 index c7d54c15..00000000 --- a/src/requests/all/edit_inline_message_caption.rs +++ /dev/null @@ -1,83 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{InlineKeyboardMarkup, ParseMode, True}, - Bot, -}; - -/// Use this method to edit captions of messages sent via the bot. -/// -/// On success, [`True`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). -/// -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditInlineMessageCaption { - #[serde(skip_serializing)] - bot: Bot, - pub inline_message_id: String, - pub caption: Option, - pub parse_mode: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for EditInlineMessageCaption { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "editMessageCaption", &self).await - } -} - -impl EditInlineMessageCaption { - pub(crate) fn new(bot: Bot, inline_message_id: I) -> Self - where - I: Into, - { - let inline_message_id = inline_message_id.into(); - Self { bot, inline_message_id, caption: None, parse_mode: None, reply_markup: None } - } - - /// Identifier of the inline message. - pub fn inline_message_id(mut self, val: T) -> Self - where - T: Into, - { - self.inline_message_id = val.into(); - self - } - - /// New caption of the message. - pub fn caption(mut self, val: T) -> Self - where - T: Into, - { - self.caption = Some(val.into()); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_inline_message_live_location.rs b/src/requests/all/edit_inline_message_live_location.rs deleted file mode 100644 index a57407c1..00000000 --- a/src/requests/all/edit_inline_message_live_location.rs +++ /dev/null @@ -1,77 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{InlineKeyboardMarkup, True}, - Bot, -}; - -/// Use this method to edit live location messages sent via the bot. -/// -/// A location can be edited until its live_period expires or editing is -/// explicitly disabled by a call to stopMessageLiveLocation. On success, -/// [`True`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation). -/// -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditInlineMessageLiveLocation { - #[serde(skip_serializing)] - bot: Bot, - pub inline_message_id: String, - pub latitude: f32, - pub longitude: f32, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for EditInlineMessageLiveLocation { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "editMessageLiveLocation", &self) - .await - } -} - -impl EditInlineMessageLiveLocation { - pub(crate) fn new(bot: Bot, inline_message_id: I, latitude: f32, longitude: f32) -> Self - where - I: Into, - { - let inline_message_id = inline_message_id.into(); - Self { bot, inline_message_id, latitude, longitude, reply_markup: None } - } - - /// Identifier of the inline message. - pub fn inline_message_id(mut self, val: T) -> Self - where - T: Into, - { - self.inline_message_id = val.into(); - self - } - - /// Latitude of new location. - pub fn latitude(mut self, val: f32) -> Self { - self.latitude = val; - self - } - - /// Longitude of new location. - pub fn longitude(mut self, val: f32) -> Self { - self.longitude = val; - self - } - - /// A JSON-serialized object for a new [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_inline_message_media.rs b/src/requests/all/edit_inline_message_media.rs deleted file mode 100644 index c61cc994..00000000 --- a/src/requests/all/edit_inline_message_media.rs +++ /dev/null @@ -1,72 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{InlineKeyboardMarkup, InputMedia, True}, - Bot, -}; - -/// Use this method to edit animation, audio, document, photo, or video -/// messages sent via the bot. -/// -/// If a message is a part of a message album, then it can be edited only to a -/// photo or a video. Otherwise, message type can be changed arbitrarily. When -/// this method is used, new file can't be uploaded. Use previously -/// uploaded file via its `file_id` or specify a URL. On success, [`True`] is -/// returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagemedia). -/// -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditInlineMessageMedia { - #[serde(skip_serializing)] - bot: Bot, - pub inline_message_id: String, - pub media: InputMedia, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for EditInlineMessageMedia { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_multipart(self.bot.client(), self.bot.token(), "editMessageMedia", self).await - } -} - -impl EditInlineMessageMedia { - pub(crate) fn new(bot: Bot, inline_message_id: I, media: InputMedia) -> Self - where - I: Into, - { - let inline_message_id = inline_message_id.into(); - Self { bot, inline_message_id, media, reply_markup: None } - } - - /// Identifier of the inline message. - pub fn inline_message_id(mut self, val: T) -> Self - where - T: Into, - { - self.inline_message_id = val.into(); - self - } - - /// A JSON-serialized object for a new media content of the message. - pub fn media(mut self, val: InputMedia) -> Self { - self.media = val; - self - } - - /// A JSON-serialized object for a new [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_inline_message_reply_markup.rs b/src/requests/all/edit_inline_message_reply_markup.rs deleted file mode 100644 index a51f4c96..00000000 --- a/src/requests/all/edit_inline_message_reply_markup.rs +++ /dev/null @@ -1,62 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{InlineKeyboardMarkup, True}, - Bot, -}; - -/// Use this method to edit only the reply markup of messages sent via the bot. -/// -/// On success, [`True`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup). -/// -/// [`Message`]: crate::types::Message -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditInlineMessageReplyMarkup { - #[serde(skip_serializing)] - bot: Bot, - pub inline_message_id: String, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for EditInlineMessageReplyMarkup { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "editMessageReplyMarkup", &self) - .await - } -} - -impl EditInlineMessageReplyMarkup { - pub(crate) fn new(bot: Bot, inline_message_id: I) -> Self - where - I: Into, - { - let inline_message_id = inline_message_id.into(); - Self { bot, inline_message_id, reply_markup: None } - } - - /// Identifier of the inline message. - pub fn inline_message_id(mut self, val: T) -> Self - where - T: Into, - { - self.inline_message_id = val.into(); - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_inline_message_text.rs b/src/requests/all/edit_inline_message_text.rs deleted file mode 100644 index d3186efd..00000000 --- a/src/requests/all/edit_inline_message_text.rs +++ /dev/null @@ -1,98 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{InlineKeyboardMarkup, Message, ParseMode}, - Bot, -}; - -/// Use this method to edit text and game messages sent via the bot. -/// -/// On success, [`True`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagetext). -/// -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditInlineMessageText { - #[serde(skip_serializing)] - bot: Bot, - pub inline_message_id: String, - pub text: String, - pub parse_mode: Option, - pub disable_web_page_preview: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for EditInlineMessageText { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "editMessageText", &self).await - } -} - -impl EditInlineMessageText { - pub(crate) fn new(bot: Bot, inline_message_id: I, text: T) -> Self - where - I: Into, - T: Into, - { - let inline_message_id = inline_message_id.into(); - let text = text.into(); - Self { - bot, - inline_message_id, - text, - parse_mode: None, - disable_web_page_preview: None, - reply_markup: None, - } - } - - /// Identifier of the inline message. - pub fn inline_message_id(mut self, val: T) -> Self - where - T: Into, - { - self.inline_message_id = val.into(); - self - } - - /// New text of the message. - pub fn text(mut self, val: T) -> Self - where - T: Into, - { - self.text = val.into(); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in your bot's message. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// Disables link previews for links in this message. - pub fn disable_web_page_preview(mut self, val: bool) -> Self { - self.disable_web_page_preview = Some(val); - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_message_caption.rs b/src/requests/all/edit_message_caption.rs deleted file mode 100644 index 5db3946b..00000000 --- a/src/requests/all/edit_message_caption.rs +++ /dev/null @@ -1,91 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, InlineKeyboardMarkup, Message, ParseMode}, - Bot, -}; - -/// Use this method to edit captions of messages sent by the bot. -/// -/// On success, the edited [`Message`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). -/// -/// [`Message`]: crate::types::Message -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditMessageCaption { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub message_id: i32, - pub caption: Option, - pub parse_mode: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for EditMessageCaption { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "editMessageCaption", &self).await - } -} - -impl EditMessageCaption { - pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id, caption: None, parse_mode: None, reply_markup: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`) - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of the message to edit - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - /// New caption of the message. - pub fn caption(mut self, val: T) -> Self - where - T: Into, - { - self.caption = Some(val.into()); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_message_live_location.rs b/src/requests/all/edit_message_live_location.rs deleted file mode 100644 index cd1d0c17..00000000 --- a/src/requests/all/edit_message_live_location.rs +++ /dev/null @@ -1,91 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, InlineKeyboardMarkup, Message}, - Bot, -}; - -/// Use this method to edit live location messages. -/// -/// A location can be edited until its live_period expires or editing is -/// explicitly disabled by a call to stopMessageLiveLocation. On success, the -/// edited [`Message`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation). -/// -/// [`Message`]: crate::types::Message -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditMessageLiveLocation { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub message_id: i32, - pub latitude: f32, - pub longitude: f32, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for EditMessageLiveLocation { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "editMessageLiveLocation", &self) - .await - } -} - -impl EditMessageLiveLocation { - pub(crate) fn new( - bot: Bot, - chat_id: C, - message_id: i32, - latitude: f32, - longitude: f32, - ) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id, latitude, longitude, reply_markup: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`) - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of the message to edit - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - /// Latitude of new location. - pub fn latitude(mut self, val: f32) -> Self { - self.latitude = val; - self - } - - /// Longitude of new location. - pub fn longitude(mut self, val: f32) -> Self { - self.longitude = val; - self - } - - /// A JSON-serialized object for a new [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_message_media.rs b/src/requests/all/edit_message_media.rs deleted file mode 100644 index 2dea170a..00000000 --- a/src/requests/all/edit_message_media.rs +++ /dev/null @@ -1,78 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, InlineKeyboardMarkup, InputMedia, Message}, - Bot, -}; - -/// Use this method to edit animation, audio, document, photo, or video -/// messages. -/// -/// If a message is a part of a message album, then it can be edited only to a -/// photo or a video. Otherwise, message type can be changed arbitrarily. On -/// success, the edited [`Message`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagemedia). -/// -/// [`Message`]: crate::types::Message -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditMessageMedia { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub message_id: i32, - pub media: InputMedia, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for EditMessageMedia { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_multipart(self.bot.client(), self.bot.token(), "editMessageMedia", self).await - } -} - -impl EditMessageMedia { - pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32, media: InputMedia) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id, media, reply_markup: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`) - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of the message to edit - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - /// A JSON-serialized object for a new media content of the message. - pub fn media(mut self, val: InputMedia) -> Self { - self.media = val; - self - } - - /// A JSON-serialized object for a new [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_message_reply_markup.rs b/src/requests/all/edit_message_reply_markup.rs deleted file mode 100644 index 5022fd5f..00000000 --- a/src/requests/all/edit_message_reply_markup.rs +++ /dev/null @@ -1,69 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, InlineKeyboardMarkup, Message}, - Bot, -}; - -/// Use this method to edit only the reply markup of messages. -/// -/// On success, the edited [`Message`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup). -/// -/// [`Message`]: crate::types::Message -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditMessageReplyMarkup { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub message_id: i32, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for EditMessageReplyMarkup { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "editMessageReplyMarkup", &self) - .await - } -} - -impl EditMessageReplyMarkup { - pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id, reply_markup: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`) - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of the message to edit - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/edit_message_text.rs b/src/requests/all/edit_message_text.rs deleted file mode 100644 index f6669411..00000000 --- a/src/requests/all/edit_message_text.rs +++ /dev/null @@ -1,107 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, InlineKeyboardMarkup, Message, ParseMode}, - Bot, -}; - -/// Use this method to edit text and game messages. -/// -/// On success, the edited [`Message`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagetext). -/// -/// [`Message`]: crate::types::Message -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditMessageText { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub message_id: i32, - pub text: String, - pub parse_mode: Option, - pub disable_web_page_preview: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for EditMessageText { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "editMessageText", &self).await - } -} - -impl EditMessageText { - pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32, text: T) -> Self - where - C: Into, - T: Into, - { - let chat_id = chat_id.into(); - let text = text.into(); - Self { - bot, - chat_id, - message_id, - text, - parse_mode: None, - disable_web_page_preview: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`) - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of the message to edit - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - /// New text of the message. - pub fn text(mut self, val: T) -> Self - where - T: Into, - { - self.text = val.into(); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in your bot's message. - /// - /// [Markdown]: https://core.telegram.org/bots/api#markdown-style - /// [HTML]: https://core.telegram.org/bots/api#html-style - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// Disables link previews for links in this message. - pub fn disable_web_page_preview(mut self, val: bool) -> Self { - self.disable_web_page_preview = Some(val); - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/export_chat_invite_link.rs b/src/requests/all/export_chat_invite_link.rs deleted file mode 100644 index 82f58a50..00000000 --- a/src/requests/all/export_chat_invite_link.rs +++ /dev/null @@ -1,65 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::ChatId, - Bot, -}; - -/// Use this method to generate a new invite link for a chat; any previously -/// generated link is revoked. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. -/// -/// ## Note -/// Each administrator in a chat generates their own invite links. Bots can't -/// use invite links generated by other administrators. If you want your bot to -/// work with invite links, it will need to generate its own link using -/// [`Bot::export_chat_invite_link`] – after this the link will become available -/// to the bot via the [`Bot::get_chat`] method. If your bot needs to generate a -/// new invite link replacing its previous one, use -/// [`Bot::export_chat_invite_link`] again. -/// -/// [The official docs](https://core.telegram.org/bots/api#exportchatinvitelink). -/// -/// [`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(Debug, Clone, Serialize)] -pub struct ExportChatInviteLink { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, -} - -#[async_trait::async_trait] -impl RequestOld for ExportChatInviteLink { - type Output = String; - - /// Returns the new invite link as `String` on success. - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "exportChatInviteLink", &self).await - } -} - -impl ExportChatInviteLink { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/forward_message.rs b/src/requests/all/forward_message.rs deleted file mode 100644 index 6df3ad43..00000000 --- a/src/requests/all/forward_message.rs +++ /dev/null @@ -1,81 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, Message}, - Bot, -}; - -/// 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(Debug, Clone, Serialize)] -pub struct ForwardMessage { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub from_chat_id: ChatId, - pub disable_notification: Option, - pub message_id: i32, -} - -#[async_trait::async_trait] -impl RequestOld for ForwardMessage { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "forwardMessage", &self).await - } -} - -impl ForwardMessage { - pub(crate) fn new(bot: Bot, chat_id: C, from_chat_id: F, message_id: i32) -> Self - where - C: Into, - F: Into, - { - let chat_id = chat_id.into(); - let from_chat_id = from_chat_id.into(); - Self { bot, chat_id, from_chat_id, message_id, disable_notification: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier for the chat where the original message was sent (or - /// channel username in the format `@channelusername`). - #[allow(clippy::wrong_self_convention)] - pub fn from_chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.from_chat_id = val.into(); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// Message identifier in the chat specified in [`from_chat_id`]. - /// - /// [`from_chat_id`]: ForwardMessage::from_chat_id - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } -} diff --git a/src/requests/all/get_chat.rs b/src/requests/all/get_chat.rs deleted file mode 100644 index 6ac5eb17..00000000 --- a/src/requests/all/get_chat.rs +++ /dev/null @@ -1,50 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{Chat, ChatId}, - Bot, -}; - -/// 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 -/// or channel, etc.). -/// -/// [The official docs](https://core.telegram.org/bots/api#getchat). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetChat { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, -} - -#[async_trait::async_trait] -impl RequestOld for GetChat { - type Output = Chat; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getChat", &self).await - } -} - -impl GetChat { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/get_chat_administrators.rs b/src/requests/all/get_chat_administrators.rs deleted file mode 100644 index e82e1162..00000000 --- a/src/requests/all/get_chat_administrators.rs +++ /dev/null @@ -1,53 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, ChatMember}, - Bot, -}; - -/// Use this method to get a list of administrators in a chat. -/// -/// If the chat is a group or a supergroup and no administrators were appointed, -/// only the creator will be returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#getchatadministrators). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetChatAdministrators { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, -} - -#[async_trait::async_trait] -impl RequestOld for GetChatAdministrators { - type Output = Vec; - - /// On success, returns an array that contains information about all chat - /// administrators except other bots. - async fn send(&self) -> ResponseResult> { - net::request_json(self.bot.client(), self.bot.token(), "getChatAdministrators", &self).await - } -} - -impl GetChatAdministrators { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/get_chat_member.rs b/src/requests/all/get_chat_member.rs deleted file mode 100644 index 9f5cc5bd..00000000 --- a/src/requests/all/get_chat_member.rs +++ /dev/null @@ -1,55 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, ChatMember}, - Bot, -}; - -/// 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(Debug, Clone, Serialize)] -pub struct GetChatMember { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub user_id: i32, -} - -#[async_trait::async_trait] -impl RequestOld for GetChatMember { - type Output = ChatMember; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getChatMember", &self).await - } -} - -impl GetChatMember { - pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, user_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } -} diff --git a/src/requests/all/get_chat_members_count.rs b/src/requests/all/get_chat_members_count.rs deleted file mode 100644 index 7cb0ec4b..00000000 --- a/src/requests/all/get_chat_members_count.rs +++ /dev/null @@ -1,48 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::ChatId, - Bot, -}; - -/// 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(Debug, Clone, Serialize)] -pub struct GetChatMembersCount { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, -} - -#[async_trait::async_trait] -impl RequestOld for GetChatMembersCount { - type Output = i32; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getChatMembersCount", &self).await - } -} - -impl GetChatMembersCount { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/get_file.rs b/src/requests/all/get_file.rs deleted file mode 100644 index bf41038e..00000000 --- a/src/requests/all/get_file.rs +++ /dev/null @@ -1,62 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::File, - Bot, -}; - -/// Use this method to get basic info about a file and prepare it for -/// downloading. -/// -/// For the moment, bots can download files of up to `20MB` in size. -/// -/// The file can then be downloaded via the link -/// `https://api.telegram.org/file/bot/`, where `` -/// is taken from the response. 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 [`GetFile`] again. -/// -/// **Note**: This function may not preserve the original file name and MIME -/// type. You should save the file's MIME type and name (if available) when the -/// [`File`] object is received. -/// -/// [The official docs](https://core.telegram.org/bots/api#getfile). -/// -/// [`File`]: crate::types::File -/// [`GetFile`]: self::GetFile -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetFile { - #[serde(skip_serializing)] - bot: Bot, - pub file_id: String, -} - -#[async_trait::async_trait] -impl RequestOld for GetFile { - type Output = File; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getFile", &self).await - } -} - -impl GetFile { - pub(crate) fn new(bot: Bot, file_id: F) -> Self - where - F: Into, - { - Self { bot, file_id: file_id.into() } - } - - /// File identifier to get info about. - pub fn file_id(mut self, value: F) -> Self - where - F: Into, - { - self.file_id = value.into(); - self - } -} diff --git a/src/requests/all/get_game_high_scores.rs b/src/requests/all/get_game_high_scores.rs deleted file mode 100644 index 3a16114e..00000000 --- a/src/requests/all/get_game_high_scores.rs +++ /dev/null @@ -1,63 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{GameHighScore, TargetMessage}, - Bot, -}; - -/// Use this method to get data for high score tables. -/// -/// Will return the score of the specified user and several of his neighbors in -/// a game. -/// -/// ## Note -/// This method will currently return scores for the target user, plus two of -/// his closest neighbors on each side. Will also return the top three users if -/// the user and his neighbors are not among them. Please note that this -/// behavior is subject to change. -/// -/// [The official docs](https://core.telegram.org/bots/api#getgamehighscores) -#[derive(Debug, Clone, Serialize)] -pub struct GetGameHighScores { - #[serde(skip_serializing)] - bot: Bot, - #[serde(flatten)] - pub target: TargetMessage, - pub user_id: i32, -} - -#[async_trait::async_trait] -impl RequestOld for GetGameHighScores { - type Output = Vec; - - async fn send(&self) -> ResponseResult> { - net::request_json(self.bot.client(), self.bot.token(), "getGameHighScores", &self).await - } -} - -impl GetGameHighScores { - pub(crate) fn new(bot: Bot, target: T, user_id: i32) -> Self - where - T: Into, - { - let target = target.into(); - Self { bot, target, user_id } - } - - /// Target message, either chat id and message id or inline message id. - pub fn target(mut self, val: T) -> Self - where - T: Into, - { - self.target = val.into(); - self - } - - /// Target user id. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } -} diff --git a/src/requests/all/get_me.rs b/src/requests/all/get_me.rs deleted file mode 100644 index 6ebbae5a..00000000 --- a/src/requests/all/get_me.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::Me, - Bot, -}; -use serde::Serialize; - -/// A simple method for testing your bot's auth token. Requires no parameters. -/// -/// [The official docs](https://core.telegram.org/bots/api#getme). -#[derive(Debug, Clone, Serialize)] -pub struct GetMe { - #[serde(skip_serializing)] - bot: Bot, -} - -#[async_trait::async_trait] -impl RequestOld for GetMe { - type Output = Me; - - /// Returns basic information about the bot. - #[allow(clippy::trivially_copy_pass_by_ref)] - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getMe", &self).await - } -} - -impl GetMe { - pub(crate) fn new(bot: Bot) -> Self { - Self { bot } - } -} diff --git a/src/requests/all/get_my_commands.rs b/src/requests/all/get_my_commands.rs deleted file mode 100644 index aa8ae4a1..00000000 --- a/src/requests/all/get_my_commands.rs +++ /dev/null @@ -1,33 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::BotCommand, - Bot, -}; - -/// Use this method to get the current list of the bot's commands. -/// -/// [The official docs](https://core.telegram.org/bots/api#getmycommands). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetMyCommands { - #[serde(skip_serializing)] - bot: Bot, -} - -#[async_trait::async_trait] -impl RequestOld for GetMyCommands { - type Output = Vec; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getMyCommands", &self).await - } -} - -impl GetMyCommands { - pub(crate) fn new(bot: Bot) -> Self { - Self { bot } - } -} diff --git a/src/requests/all/get_sticker_set.rs b/src/requests/all/get_sticker_set.rs deleted file mode 100644 index 5d05e1d2..00000000 --- a/src/requests/all/get_sticker_set.rs +++ /dev/null @@ -1,47 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::StickerSet, - Bot, -}; - -/// 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(Debug, Clone, Serialize)] -pub struct GetStickerSet { - #[serde(skip_serializing)] - bot: Bot, - pub name: String, -} - -#[async_trait::async_trait] -impl RequestOld for GetStickerSet { - type Output = StickerSet; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getStickerSet", &self).await - } -} - -impl GetStickerSet { - pub(crate) fn new(bot: Bot, name: N) -> Self - where - N: Into, - { - let name = name.into(); - Self { bot, name } - } - - /// Name of the sticker set. - pub fn name(mut self, val: T) -> Self - where - T: Into, - { - self.name = val.into(); - self - } -} diff --git a/src/requests/all/get_updates.rs b/src/requests/all/get_updates.rs deleted file mode 100644 index fa635f73..00000000 --- a/src/requests/all/get_updates.rs +++ /dev/null @@ -1,106 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{AllowedUpdate, Update}, - Bot, -}; - -/// Use this method to receive incoming updates using long polling ([wiki]). -/// -/// **Notes:** -/// 1. This method will not work if an outgoing webhook is set up. -/// 2. In order to avoid getting duplicate updates, -/// recalculate offset after each server response. -/// -/// [The official docs](https://core.telegram.org/bots/api#getupdates). -/// -/// [wiki]: https://en.wikipedia.org/wiki/Push_technology#Long_polling -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetUpdates { - #[serde(skip_serializing)] - pub(crate) bot: Bot, - pub offset: Option, - pub limit: Option, - pub timeout: Option, - pub allowed_updates: Option>, -} - -#[async_trait::async_trait] -impl RequestOld for GetUpdates { - type Output = Vec; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getUpdates", &self).await - } -} - -impl GetUpdates { - pub(crate) fn new(bot: Bot) -> Self { - Self { bot, offset: None, limit: None, timeout: None, allowed_updates: None } - } - - /// Identifier of the first update to be returned. - /// - /// Must be greater by one than the highest among the identifiers of - /// previously received updates. By default, updates starting with the - /// earliest unconfirmed update are returned. An update is considered - /// confirmed as soon as [`GetUpdates`] is called with an [`offset`] - /// higher than its [`id`]. The negative offset can be specified to - /// retrieve updates starting from `-offset` update from the end of the - /// updates queue. All previous updates will forgotten. - /// - /// [`GetUpdates`]: self::GetUpdates - /// [`offset`]: self::GetUpdates::offset - /// [`id`]: crate::types::Update::id - pub fn offset(mut self, value: i32) -> Self { - self.offset = Some(value); - self - } - - /// Limits the number of updates to be retrieved. - /// - /// Values between `1`—`100` are accepted. Defaults to `100`. - pub fn limit(mut self, value: u8) -> Self { - self.limit = Some(value); - self - } - - /// Timeout in seconds for long polling. - /// - /// Defaults to `0`, i.e. usual short polling. Should be positive, short - /// polling should be used for testing purposes only. - pub fn timeout(mut self, value: u32) -> Self { - self.timeout = Some(value); - self - } - - /// List the types of updates you want your bot to receive. - /// - /// For example, specify [[`Message`], [`EditedChannelPost`], - /// [`CallbackQuery`]] to only receive updates of these types. - /// See [`AllowedUpdate`] for a complete list of available update types. - /// - /// Specify an empty list to receive all updates regardless of type - /// (default). If not specified, the previous setting will be used. - /// - /// **Note:** - /// This parameter doesn't affect updates created before the call to the - /// [`Bot::get_updates`], so unwanted updates may be received for a short - /// period of time. - /// - /// [`Message`]: self::AllowedUpdate::Message - /// [`EditedChannelPost`]: self::AllowedUpdate::EditedChannelPost - /// [`CallbackQuery`]: self::AllowedUpdate::CallbackQuery - /// [`AllowedUpdate`]: self::AllowedUpdate - /// [`Bot::get_updates`]: crate::Bot::get_updates - pub fn allowed_updates(mut self, value: T) -> Self - where - T: Into>, - { - self.allowed_updates = Some(value.into()); - self - } -} diff --git a/src/requests/all/get_updates_non_strict.rs b/src/requests/all/get_updates_non_strict.rs deleted file mode 100644 index 77b7db44..00000000 --- a/src/requests/all/get_updates_non_strict.rs +++ /dev/null @@ -1,97 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{GetUpdates, RequestOld, ResponseResult}, - types::{AllowedUpdate, NonStrictVec, Update}, - Bot, -}; - -/// This is non strict version of [`GetUpdates`], this means that if it will -/// fail to deserialize some updates, it won't fail entirely, but will just -/// return some errors. -/// -/// Note: this is not a 'real' telegram method, this is simply [`GetUpdates`] -/// with changed return type. -/// -/// [`GetUpdates`]: crate::requests::GetUpdates -#[derive(Debug, Clone, Serialize)] -#[serde(transparent)] -pub struct GetUpdatesNonStrict(pub GetUpdates); - -#[async_trait::async_trait] -impl RequestOld for GetUpdatesNonStrict { - type Output = NonStrictVec; - - async fn send(&self) -> ResponseResult { - net::request_json(self.0.bot.client(), self.0.bot.token(), "getUpdates", &self).await - } -} - -impl GetUpdatesNonStrict { - pub(crate) fn new(bot: Bot) -> Self { - Self(GetUpdates::new(bot)) - } - - /// Identifier of the first update to be returned. - /// - /// Must be greater by one than the highest among the identifiers of - /// previously received updates. By default, updates starting with the - /// earliest unconfirmed update are returned. An update is considered - /// confirmed as soon as [`GetUpdates`] is called with an [`offset`] - /// higher than its [`id`]. The negative offset can be specified to - /// retrieve updates starting from `-offset` update from the end of the - /// updates queue. All previous updates will forgotten. - /// - /// [`GetUpdates`]: self::GetUpdates - /// [`offset`]: self::GetUpdates::offset - /// [`id`]: crate::types::Update::id - pub fn offset(mut self, value: i32) -> Self { - self.0.offset = Some(value); - self - } - - /// Limits the number of updates to be retrieved. - /// - /// Values between `1`—`100` are accepted. Defaults to `100`. - pub fn limit(mut self, value: u8) -> Self { - self.0.limit = Some(value); - self - } - - /// Timeout in seconds for long polling. - /// - /// Defaults to `0`, i.e. usual short polling. Should be positive, short - /// polling should be used for testing purposes only. - pub fn timeout(mut self, value: u32) -> Self { - self.0.timeout = Some(value); - self - } - - /// List the types of updates you want your bot to receive. - /// - /// For example, specify [[`Message`], [`EditedChannelPost`], - /// [`CallbackQuery`]] to only receive updates of these types. - /// See [`AllowedUpdate`] for a complete list of available update types. - /// - /// Specify an empty list to receive all updates regardless of type - /// (default). If not specified, the previous setting will be used. - /// - /// **Note:** - /// This parameter doesn't affect updates created before the call to the - /// [`Bot::get_updates`], so unwanted updates may be received for a short - /// period of time. - /// - /// [`Message`]: self::AllowedUpdate::Message - /// [`EditedChannelPost`]: self::AllowedUpdate::EditedChannelPost - /// [`CallbackQuery`]: self::AllowedUpdate::CallbackQuery - /// [`AllowedUpdate`]: self::AllowedUpdate - /// [`Bot::get_updates`]: crate::Bot::get_updates - pub fn allowed_updates(mut self, value: T) -> Self - where - T: Into>, - { - self.0.allowed_updates = Some(value.into()); - self - } -} diff --git a/src/requests/all/get_user_profile_photos.rs b/src/requests/all/get_user_profile_photos.rs deleted file mode 100644 index b391ca51..00000000 --- a/src/requests/all/get_user_profile_photos.rs +++ /dev/null @@ -1,58 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::UserProfilePhotos, - Bot, -}; - -/// 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(Debug, Clone, Serialize)] -pub struct GetUserProfilePhotos { - #[serde(skip_serializing)] - bot: Bot, - pub user_id: i32, - pub offset: Option, - pub limit: Option, -} - -#[async_trait::async_trait] -impl RequestOld for GetUserProfilePhotos { - type Output = UserProfilePhotos; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getUserProfilePhotos", &self).await - } -} - -impl GetUserProfilePhotos { - pub(crate) fn new(bot: Bot, user_id: i32) -> Self { - Self { bot, user_id, offset: None, limit: None } - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// Sequential number of the first photo to be returned. By default, all - /// photos are returned. - pub fn offset(mut self, val: i32) -> Self { - self.offset = Some(val); - self - } - - /// Limits the number of photos to be retrieved. Values between 1—100 are - /// accepted. - /// - /// Defaults to 100. - pub fn limit(mut self, val: i32) -> Self { - self.limit = Some(val); - self - } -} diff --git a/src/requests/all/get_webhook_info.rs b/src/requests/all/get_webhook_info.rs deleted file mode 100644 index 086b7464..00000000 --- a/src/requests/all/get_webhook_info.rs +++ /dev/null @@ -1,38 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::WebhookInfo, - Bot, -}; - -/// Use this method to get current webhook status. -/// -/// If the bot is using [`Bot::get_updates`], will return an object with the url -/// field empty. -/// -/// [The official docs](https://core.telegram.org/bots/api#getwebhookinfo). -/// -/// [`Bot::get_updates`]: crate::Bot::get_updates -#[derive(Debug, Clone, Serialize)] -pub struct GetWebhookInfo { - #[serde(skip_serializing)] - bot: Bot, -} - -#[async_trait::async_trait] -impl RequestOld for GetWebhookInfo { - type Output = WebhookInfo; - - #[allow(clippy::trivially_copy_pass_by_ref)] - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "getWebhookInfo", &self).await - } -} - -impl GetWebhookInfo { - pub(crate) fn new(bot: Bot) -> Self { - Self { bot } - } -} diff --git a/src/requests/all/kick_chat_member.rs b/src/requests/all/kick_chat_member.rs deleted file mode 100644 index 455f160c..00000000 --- a/src/requests/all/kick_chat_member.rs +++ /dev/null @@ -1,72 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to kick a user from a group, a supergroup or a channel. -/// -/// In the case of supergroups and channels, the user will not be able to return -/// to the group on their own using invite links, etc., unless [unbanned] first. -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#kickchatmember). -/// -/// [unbanned]: crate::Bot::unban_chat_member -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct KickChatMember { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub user_id: i32, - pub until_date: Option, -} - -#[async_trait::async_trait] -impl RequestOld for KickChatMember { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "kickChatMember", &self).await - } -} - -impl KickChatMember { - pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, user_id, until_date: None } - } - - /// Unique identifier for the target group or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// Date when the user will be unbanned, unix time. - /// - /// If user is banned for more than 366 days or less than 30 seconds from - /// the current time they are considered to be banned forever. - pub fn until_date(mut self, val: i32) -> Self { - self.until_date = Some(val); - self - } -} diff --git a/src/requests/all/leave_chat.rs b/src/requests/all/leave_chat.rs deleted file mode 100644 index 6b96f621..00000000 --- a/src/requests/all/leave_chat.rs +++ /dev/null @@ -1,48 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// 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(Debug, Clone, Serialize)] -pub struct LeaveChat { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, -} - -#[async_trait::async_trait] -impl RequestOld for LeaveChat { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "leaveChat", &self).await - } -} - -impl LeaveChat { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/mod.rs b/src/requests/all/mod.rs deleted file mode 100644 index d173f210..00000000 --- a/src/requests/all/mod.rs +++ /dev/null @@ -1,154 +0,0 @@ -mod add_sticker_to_set; -mod answer_callback_query; -mod answer_inline_query; -mod answer_pre_checkout_query; -mod answer_shipping_query; -mod create_new_sticker_set; -mod delete_chat_photo; -mod delete_chat_sticker_set; -mod delete_message; -mod delete_sticker_from_set; -mod delete_webhook; -mod edit_inline_message_caption; -mod edit_inline_message_live_location; -mod edit_inline_message_media; -mod edit_inline_message_reply_markup; -mod edit_inline_message_text; -mod edit_message_caption; -mod edit_message_live_location; -mod edit_message_media; -mod edit_message_reply_markup; -mod edit_message_text; -mod export_chat_invite_link; -mod forward_message; -mod get_chat; -mod get_chat_administrators; -mod get_chat_member; -mod get_chat_members_count; -mod get_file; -mod get_game_high_scores; -mod get_me; -mod get_my_commands; -mod get_sticker_set; -mod get_updates; -mod get_updates_non_strict; -mod get_user_profile_photos; -mod get_webhook_info; -mod kick_chat_member; -mod leave_chat; -mod pin_chat_message; -mod promote_chat_member; -mod restrict_chat_member; -mod send_animation; -mod send_audio; -mod send_chat_action; -mod send_contact; -mod send_dice; -mod send_document; -mod send_game; -mod send_invoice; -mod send_location; -mod send_media_group; -mod send_message; -mod send_photo; -mod send_poll; -mod send_sticker; -mod send_venue; -mod send_video; -mod send_video_note; -mod send_voice; -mod set_chat_administrator_custom_title; -mod set_chat_description; -mod set_chat_permissions; -mod set_chat_photo; -mod set_chat_sticker_set; -mod set_chat_title; -mod set_game_score; -mod set_my_commands; -mod set_sticker_position_in_set; -mod set_sticker_set_thumb; -mod set_webhook; -mod stop_inline_message_live_location; -mod stop_message_live_location; -mod stop_poll; -mod unban_chat_member; -mod unpin_chat_message; -mod upload_sticker_file; - -pub use add_sticker_to_set::*; -pub use answer_callback_query::*; -pub use answer_inline_query::*; -pub use answer_pre_checkout_query::*; -pub use answer_shipping_query::*; -pub use create_new_sticker_set::*; -pub use delete_chat_photo::*; -pub use delete_chat_sticker_set::*; -pub use delete_message::*; -pub use delete_sticker_from_set::*; -pub use delete_webhook::*; -pub use edit_inline_message_caption::*; -pub use edit_inline_message_live_location::*; -pub use edit_inline_message_media::*; -pub use edit_inline_message_reply_markup::*; -pub use edit_inline_message_text::*; -pub use edit_message_caption::*; -pub use edit_message_live_location::*; -pub use edit_message_media::*; -pub use edit_message_reply_markup::*; -pub use edit_message_text::*; -pub use export_chat_invite_link::*; -pub use forward_message::*; -pub use get_chat::*; -pub use get_chat_administrators::*; -pub use get_chat_member::*; -pub use get_chat_members_count::*; -pub use get_file::*; -pub use get_game_high_scores::*; -pub use get_me::*; -pub use get_my_commands::*; -pub use get_sticker_set::*; -pub use get_updates::*; -pub use get_updates_non_strict::*; -pub use get_user_profile_photos::*; -pub use get_webhook_info::*; -pub use kick_chat_member::*; -pub use leave_chat::*; -pub use pin_chat_message::*; -pub use promote_chat_member::*; -pub use restrict_chat_member::*; -pub use send_animation::*; -pub use send_audio::*; -pub use send_chat_action::*; -pub use send_contact::*; -pub use send_dice::*; -pub use send_document::*; -pub use send_game::*; -pub use send_invoice::*; -pub use send_location::*; -pub use send_media_group::*; -pub use send_message::*; -pub use send_photo::*; -pub use send_poll::*; -pub use send_sticker::*; -pub use send_venue::*; -pub use send_video::*; -pub use send_video_note::*; -pub use send_voice::*; -pub use set_chat_administrator_custom_title::*; -pub use set_chat_description::*; -pub use set_chat_permissions::*; -pub use set_chat_photo::*; -pub use set_chat_sticker_set::*; -pub use set_chat_title::*; -pub use set_game_score::*; -pub use set_my_commands::*; -pub use set_sticker_position_in_set::*; -pub use set_sticker_set_thumb::*; -pub use set_webhook::*; -pub use std::pin::Pin; -pub use stop_inline_message_live_location::*; -pub use stop_message_live_location::*; -pub use stop_poll::*; -pub use unban_chat_member::*; -pub use unpin_chat_message::*; -pub use upload_sticker_file::*; diff --git a/src/requests/all/pin_chat_message.rs b/src/requests/all/pin_chat_message.rs deleted file mode 100644 index d7aa8586..00000000 --- a/src/requests/all/pin_chat_message.rs +++ /dev/null @@ -1,69 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to pin a message in a group, a supergroup, or a channel. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the `can_pin_messages` admin right in the supergroup or `can_edit_messages` -/// admin right in the channel. -/// -/// [The official docs](https://core.telegram.org/bots/api#pinchatmessage). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct PinChatMessage { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub message_id: i32, - pub disable_notification: Option, -} - -#[async_trait::async_trait] -impl RequestOld for PinChatMessage { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "pinChatMessage", &self).await - } -} - -impl PinChatMessage { - pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id, disable_notification: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of a message to pin. - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - /// Pass `true`, if it is not necessary to send a notification to all chat - /// members about the new pinned message. - /// - /// Notifications are always disabled in channels. - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } -} diff --git a/src/requests/all/promote_chat_member.rs b/src/requests/all/promote_chat_member.rs deleted file mode 100644 index 2f558dc9..00000000 --- a/src/requests/all/promote_chat_member.rs +++ /dev/null @@ -1,134 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to promote or demote a user in a supergroup or a channel. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. Pass False for all boolean parameters to -/// demote a user. -/// -/// [The official docs](https://core.telegram.org/bots/api#promotechatmember). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct PromoteChatMember { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub user_id: i32, - pub can_change_info: Option, - pub can_post_messages: Option, - pub can_edit_messages: Option, - pub can_delete_messages: Option, - pub can_invite_users: Option, - pub can_restrict_members: Option, - pub can_pin_messages: Option, - pub can_promote_members: Option, -} - -#[async_trait::async_trait] -impl RequestOld for PromoteChatMember { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "promoteChatMember", &self).await - } -} - -impl PromoteChatMember { - pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { - bot, - chat_id, - user_id, - can_change_info: None, - can_post_messages: None, - can_edit_messages: None, - can_delete_messages: None, - can_invite_users: None, - can_restrict_members: None, - can_pin_messages: None, - can_promote_members: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// Pass `true`, if the administrator can change chat title, photo and other - /// settings. - pub fn can_change_info(mut self, val: bool) -> Self { - self.can_change_info = Some(val); - self - } - - /// Pass `true`, if the administrator can create channel posts, channels - /// only. - pub fn can_post_messages(mut self, val: bool) -> Self { - self.can_post_messages = Some(val); - self - } - - /// Pass `true`, if the administrator can edit messages of other users and - /// can pin messages, channels only. - pub fn can_edit_messages(mut self, val: bool) -> Self { - self.can_edit_messages = Some(val); - self - } - - /// Pass `true`, if the administrator can delete messages of other users. - pub fn can_delete_messages(mut self, val: bool) -> Self { - self.can_delete_messages = Some(val); - self - } - - /// Pass `true`, if the administrator can invite new users to the chat. - pub fn can_invite_users(mut self, val: bool) -> Self { - self.can_invite_users = Some(val); - self - } - - /// Pass `true`, if the administrator can restrict, ban or unban chat - /// members. - pub fn can_restrict_members(mut self, val: bool) -> Self { - self.can_restrict_members = Some(val); - self - } - - /// Pass `true`, if the administrator can pin messages, supergroups only. - pub fn can_pin_messages(mut self, val: bool) -> Self { - self.can_pin_messages = Some(val); - self - } - - /// Pass `true`, if the administrator can add new administrators with a - /// subset of his own privileges or demote administrators that he has - /// promoted, directly or indirectly (promoted by administrators that were - /// appointed by him). - pub fn can_promote_members(mut self, val: bool) -> Self { - self.can_promote_members = Some(val); - self - } -} diff --git a/src/requests/all/restrict_chat_member.rs b/src/requests/all/restrict_chat_member.rs deleted file mode 100644 index 84caa542..00000000 --- a/src/requests/all/restrict_chat_member.rs +++ /dev/null @@ -1,76 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, ChatPermissions, True}, - Bot, -}; - -/// Use this method to restrict a user in a supergroup. -/// -/// The bot must be an administrator in the supergroup for this to work and must -/// have the appropriate admin rights. Pass `true` for all permissions to lift -/// restrictions from a user. -/// -/// [The official docs](https://core.telegram.org/bots/api#restrictchatmember). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct RestrictChatMember { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub user_id: i32, - pub permissions: ChatPermissions, - pub until_date: Option, -} - -#[async_trait::async_trait] -impl RequestOld for RestrictChatMember { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "restrictChatMember", &self).await - } -} - -impl RestrictChatMember { - pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32, permissions: ChatPermissions) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, user_id, permissions, until_date: None } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup (in the format `@supergroupusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// New user permissions. - pub fn permissions(mut self, val: ChatPermissions) -> Self { - self.permissions = val; - self - } - - /// Date when restrictions will be lifted for the user, unix time. - /// - /// If user is restricted for more than 366 days or less than 30 seconds - /// from the current time, they are considered to be restricted forever. - pub fn until_date(mut self, val: i32) -> Self { - self.until_date = Some(val); - self - } -} diff --git a/src/requests/all/send_animation.rs b/src/requests/all/send_animation.rs deleted file mode 100644 index f8ef0b80..00000000 --- a/src/requests/all/send_animation.rs +++ /dev/null @@ -1,158 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video -/// without sound). -/// -/// Bots can currently send animation files of up to 50 MB in size, this limit -/// may be changed in the future. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendanimation). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendAnimation { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub animation: InputFile, - pub duration: Option, - pub width: Option, - pub height: Option, - pub thumb: Option, - pub caption: Option, - pub parse_mode: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SendAnimation { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_multipart(self.bot.client(), self.bot.token(), "sendAnimation", self).await - } -} - -impl SendAnimation { - pub(crate) fn new(bot: Bot, chat_id: C, animation: InputFile) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - animation, - duration: None, - width: None, - height: None, - thumb: None, - caption: None, - parse_mode: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, value: T) -> Self - where - T: Into, - { - self.chat_id = value.into(); - self - } - - /// Animation to send. - pub fn animation(mut self, val: InputFile) -> Self { - self.animation = val; - self - } - - /// Duration of sent animation in seconds. - pub fn duration(mut self, value: u32) -> Self { - self.duration = Some(value); - self - } - - /// Animation width. - pub fn width(mut self, value: u32) -> Self { - self.width = Some(value); - self - } - - /// Animation height. - pub fn height(mut self, value: u32) -> Self { - self.height = Some(value); - self - } - - /// Thumbnail of the file sent; can be ignored if thumbnail generation for - /// the file is supported server-side. - /// - /// The thumbnail should be in JPEG format and less than 200 kB in size. A - /// thumbnail‘s width and height should not exceed 320. Ignored if the - /// file is not uploaded using [`InputFile::File`]. Thumbnails can’t be - /// reused and can be only uploaded as a new file, with - /// [`InputFile::File`]. - /// - /// [`InputFile::File`]: crate::types::InputFile::File - pub fn thumb(mut self, value: InputFile) -> Self { - self.thumb = Some(value); - self - } - - /// Animation caption, `0`-`1024` characters. - pub fn caption(mut self, value: T) -> Self - where - T: Into, - { - self.caption = Some(value.into()); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, value: ParseMode) -> Self { - self.parse_mode = Some(value); - self - } - - /// Sends the message silently. Users will receive a notification with no - /// sound. - pub fn disable_notification(mut self, value: bool) -> Self { - self.disable_notification = Some(value); - self - } - - /// If the message is a reply, [id] of the original message. - /// - /// [id]: crate::types::Message::id - pub fn reply_to_message_id(mut self, value: i32) -> Self { - self.reply_to_message_id = Some(value); - self - } - - /// Additional interface options. - pub fn reply_markup(mut self, value: T) -> Self - where - T: Into, - { - self.reply_markup = Some(value.into()); - self - } -} diff --git a/src/requests/all/send_audio.rs b/src/requests/all/send_audio.rs deleted file mode 100644 index ba7ac093..00000000 --- a/src/requests/all/send_audio.rs +++ /dev/null @@ -1,183 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send audio files, if you want Telegram clients to display -/// them in the music player. -/// -/// Your audio must be in the .MP3 or .M4A format. Bots can currently send audio -/// files of up to 50 MB in size, this limit may be changed in the future. -/// -/// For sending voice messages, use the [`Bot::send_voice`] method instead. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendaudio). -/// -/// [`Bot::send_voice`]: crate::Bot::send_voice -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendAudio { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub audio: InputFile, - pub caption: Option, - pub parse_mode: Option, - pub duration: Option, - pub performer: Option, - pub title: Option, - pub thumb: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SendAudio { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_multipart(self.bot.client(), self.bot.token(), "sendAudio", self).await - } -} - -impl SendAudio { - pub(crate) fn new(bot: Bot, chat_id: C, audio: InputFile) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - audio, - caption: None, - parse_mode: None, - duration: None, - performer: None, - title: None, - thumb: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Audio file to send. - /// - /// Pass [`InputFile::File`] to send a file that exists on - /// the Telegram servers (recommended), pass an [`InputFile::Url`] for - /// Telegram to get a .webp file from the Internet, or upload a new one - /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. - /// - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`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 audio(mut self, val: InputFile) -> Self { - self.audio = val; - self - } - - /// Audio caption, 0-1024 characters. - pub fn caption(mut self, val: T) -> Self - where - T: Into, - { - self.caption = Some(val.into()); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// Duration of the audio in seconds. - pub fn duration(mut self, val: i32) -> Self { - self.duration = Some(val); - self - } - - /// Performer. - pub fn performer(mut self, val: T) -> Self - where - T: Into, - { - self.performer = Some(val.into()); - self - } - - /// Track name. - pub fn title(mut self, val: T) -> Self - where - T: Into, - { - self.title = Some(val.into()); - self - } - - /// Thumbnail of the file sent; can be ignored if thumbnail generation for - /// the file is supported server-side. - /// - /// The thumbnail should be in JPEG format and less than 200 kB in size. A - /// thumbnail‘s width and height should not exceed 320. Ignored if the - /// file is not uploaded using `multipart/form-data`. Thumbnails can’t - /// be reused and can be only uploaded as a new file, so you can pass - /// `attach://` if the thumbnail was uploaded using - /// `multipart/form-data` under ``. [More info on - /// Sending Files Âģ]. - /// - /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. A JSON-serialized object for an [inline - /// keyboard], [custom reply keyboard], instructions to remove reply - /// keyboard or to force a reply from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_chat_action.rs b/src/requests/all/send_chat_action.rs deleted file mode 100644 index e83df54d..00000000 --- a/src/requests/all/send_chat_action.rs +++ /dev/null @@ -1,105 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method when you need to tell the user that something is happening -/// on the bot's side. -/// -/// The status is set for 5 seconds or less (when a message arrives from your -/// bot, Telegram clients clear its typing status). -/// -/// ## Note -/// Example: The [ImageBot] needs some time to process a request and upload the -/// image. Instead of sending a text message along the lines of “Retrieving -/// image, please waitâ€Ļ”, the bot may use [`Bot::send_chat_action`] with `action -/// = upload_photo`. The user will see a `sending photo` status for the bot. -/// -/// We only recommend using this method when a response from the bot will take a -/// **noticeable** amount of time to arrive. -/// -/// [ImageBot]: https://t.me/imagebot -/// [`Bot::send_chat_action`]: crate::Bot::send_chat_action -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendChatAction { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub action: SendChatActionKind, -} - -/// A type of action used in [`SendChatAction`]. -/// -/// [`SendChatAction`]: crate::requests::SendChatAction -#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum SendChatActionKind { - /// For [text messages](crate::Bot::send_message). - Typing, - - /// For [photos](crate::Bot::send_photo). - UploadPhoto, - - /// For [videos](crate::Bot::send_video). - RecordVideo, - - /// For [videos](crate::Bot::send_video). - UploadVideo, - - /// For [audio files](crate::Bot::send_audio). - RecordAudio, - - /// For [audio files](crate::Bot::send_audio). - UploadAudio, - - /// For [general files](crate::Bot::send_document). - UploadDocument, - - /// For [location data](crate::Bot::send_location). - FindLocation, - - /// For [video notes](crate::Bot::send_video_note). - RecordVideoNote, - - /// For [video notes](crate::Bot::send_video_note). - UploadVideoNote, -} - -#[async_trait::async_trait] -impl RequestOld for SendChatAction { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendChatAction", &self).await - } -} - -impl SendChatAction { - pub(crate) fn new(bot: Bot, chat_id: C, action: SendChatActionKind) -> Self - where - C: Into, - { - Self { bot, chat_id: chat_id.into(), action } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Type of action to broadcast. - pub fn action(mut self, val: SendChatActionKind) -> Self { - self.action = val; - self - } -} diff --git a/src/requests/all/send_contact.rs b/src/requests/all/send_contact.rs deleted file mode 100644 index 5eb27e95..00000000 --- a/src/requests/all/send_contact.rs +++ /dev/null @@ -1,129 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, Message, ReplyMarkup}, - Bot, -}; - -/// Use this method to send phone contacts. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendcontact). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendContact { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub phone_number: String, - pub first_name: String, - pub last_name: Option, - pub vcard: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SendContact { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendContact", &self).await - } -} - -impl SendContact { - pub(crate) fn new(bot: Bot, chat_id: C, phone_number: P, first_name: F) -> Self - where - C: Into, - P: Into, - F: Into, - { - let chat_id = chat_id.into(); - let phone_number = phone_number.into(); - let first_name = first_name.into(); - Self { - bot, - chat_id, - phone_number, - first_name, - last_name: None, - vcard: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Contact's phone number. - pub fn phone_number(mut self, val: T) -> Self - where - T: Into, - { - self.phone_number = val.into(); - self - } - - /// Contact's first name. - pub fn first_name(mut self, val: T) -> Self - where - T: Into, - { - self.first_name = val.into(); - self - } - - /// Contact's last name. - pub fn last_name(mut self, val: T) -> Self - where - T: Into, - { - self.last_name = Some(val.into()); - self - } - - /// Additional data about the contact in the form of a [vCard], 0-2048 - /// bytes. - /// - /// [vCard]: https://en.wikipedia.org/wiki/VCard - pub fn vcard(mut self, val: T) -> Self - where - T: Into, - { - self.vcard = Some(val.into()); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_dice.rs b/src/requests/all/send_dice.rs deleted file mode 100644 index 3a650d7f..00000000 --- a/src/requests/all/send_dice.rs +++ /dev/null @@ -1,93 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, DiceEmoji, Message, ReplyMarkup}, - Bot, -}; - -/// Use this method to send an animated emoji that will display a random value. -/// -/// [The official docs](https://core.telegram.org/bots/api#senddice). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendDice { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - #[serde(flatten)] - pub emoji: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SendDice { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendDice", &self).await - } -} - -impl SendDice { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - emoji: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, value: T) -> Self - where - T: Into, - { - self.chat_id = value.into(); - self - } - - /// Emoji on which the dice throw animation is based. - pub fn emoji(mut self, val: DiceEmoji) -> Self { - self.emoji = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, value: bool) -> Self { - self.disable_notification = Some(value); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, value: i32) -> Self { - self.reply_to_message_id = Some(value); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_document.rs b/src/requests/all/send_document.rs deleted file mode 100644 index 0e766ac8..00000000 --- a/src/requests/all/send_document.rs +++ /dev/null @@ -1,140 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send general files. -/// -/// Bots can currently send files of any type of up to 50 MB in size, this limit -/// may be changed in the future. -/// -/// [The official docs](https://core.telegram.org/bots/api#senddocument). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendDocument { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub document: InputFile, - pub thumb: Option, - pub caption: Option, - pub parse_mode: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SendDocument { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_multipart(self.bot.client(), self.bot.token(), "sendDocument", self).await - } -} - -impl SendDocument { - pub(crate) fn new(bot: Bot, chat_id: C, document: InputFile) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - document, - thumb: None, - caption: None, - parse_mode: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// File to send. - /// - /// Pass a file_id as String to send a file that exists on the - /// Telegram servers (recommended), pass an HTTP URL as a String for - /// Telegram to get a file from the Internet, or upload a new one using - /// `multipart/form-data`. [More info on Sending Files Âģ]. - /// - /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files - pub fn document(mut self, val: InputFile) -> Self { - self.document = val; - self - } - - /// Thumbnail of the file sent; can be ignored if thumbnail generation for - /// the file is supported server-side. - /// - /// The thumbnail should be in JPEG format and less than 200 kB in size. A - /// thumbnail‘s width and height should not exceed 320. Ignored if the - /// file is not uploaded using `multipart/form-data`. Thumbnails can’t - /// be reused and can be only uploaded as a new file, so you can pass - /// “attach://” if the thumbnail was uploaded using - /// `multipart/form-data` under ``. [More info on - /// Sending Files Âģ]. - /// - /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - /// Document caption (may also be used when resending documents by - /// `file_id`), 0-1024 characters. - pub fn caption(mut self, val: T) -> Self - where - T: Into, - { - self.caption = Some(val.into()); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_game.rs b/src/requests/all/send_game.rs deleted file mode 100644 index bde1bd1c..00000000 --- a/src/requests/all/send_game.rs +++ /dev/null @@ -1,92 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{InlineKeyboardMarkup, Message}, - Bot, -}; - -/// Use this method to send a game. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendgame). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendGame { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: i32, - pub game_short_name: String, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SendGame { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendGame", &self).await - } -} - -impl SendGame { - pub(crate) fn new(bot: Bot, chat_id: i32, game_short_name: G) -> Self - where - G: Into, - { - let game_short_name = game_short_name.into(); - Self { - bot, - chat_id, - game_short_name, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat. - pub fn chat_id(mut self, val: i32) -> Self { - self.chat_id = val; - self - } - - /// Short name of the game, serves as the unique identifier for the game. - /// Set up your games via [@Botfather]. - /// - /// [@Botfather]: https://t.me/botfather - pub fn game_short_name(mut self, val: T) -> Self - where - T: Into, - { - self.game_short_name = val.into(); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// A JSON-serialized object for an [inline keyboard]. If empty, one `Play - /// game_title` button will be shown. If not empty, the first button must - /// launch the game. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_invoice.rs b/src/requests/all/send_invoice.rs deleted file mode 100644 index bce7aa5d..00000000 --- a/src/requests/all/send_invoice.rs +++ /dev/null @@ -1,299 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{InlineKeyboardMarkup, LabeledPrice, Message}, - Bot, -}; - -/// Use this method to send invoices. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendinvoice). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendInvoice { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: i32, - pub title: String, - pub description: String, - pub payload: String, - pub provider_token: String, - pub start_parameter: String, - pub currency: String, - pub prices: Vec, - pub provider_data: Option, - pub photo_url: Option, - pub photo_size: Option, - pub photo_width: Option, - pub photo_height: Option, - pub need_name: Option, - pub need_phone_number: Option, - pub need_email: Option, - pub need_shipping_address: Option, - pub send_phone_number_to_provider: Option, - pub send_email_to_provider: Option, - pub is_flexible: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SendInvoice { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendInvoice", &self).await - } -} - -impl SendInvoice { - #[allow(clippy::too_many_arguments)] - pub(crate) fn new( - bot: Bot, - chat_id: i32, - title: T, - description: D, - payload: Pl, - provider_token: Pt, - start_parameter: S, - currency: C, - prices: Pr, - ) -> Self - where - T: Into, - D: Into, - Pl: Into, - Pt: Into, - S: Into, - C: Into, - Pr: Into>, - { - let title = title.into(); - let description = description.into(); - let payload = payload.into(); - let provider_token = provider_token.into(); - let start_parameter = start_parameter.into(); - let currency = currency.into(); - let prices = prices.into(); - Self { - bot, - chat_id, - title, - description, - payload, - provider_token, - start_parameter, - currency, - prices, - provider_data: None, - photo_url: None, - photo_size: None, - photo_width: None, - photo_height: None, - need_name: None, - need_phone_number: None, - need_email: None, - need_shipping_address: None, - send_phone_number_to_provider: None, - send_email_to_provider: None, - is_flexible: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target private chat. - pub fn chat_id(mut self, val: i32) -> Self { - self.chat_id = val; - self - } - - /// Product name, 1-32 characters. - pub fn title(mut self, val: T) -> Self - where - T: Into, - { - self.title = val.into(); - self - } - - /// Product description, 1-255 characters. - pub fn description(mut self, val: T) -> Self - where - T: Into, - { - self.description = val.into(); - self - } - - /// Bot-defined invoice payload, 1-128 bytes. This will not be displayed to - /// the user, use for your internal processes. - pub fn payload(mut self, val: T) -> Self - where - T: Into, - { - self.payload = val.into(); - self - } - - /// Payments provider token, obtained via [@Botfather]. - /// - /// [@Botfather]: https://t.me/botfather - pub fn provider_token(mut self, val: T) -> Self - where - T: Into, - { - self.provider_token = val.into(); - self - } - - /// Unique deep-linking parameter that can be used to generate this invoice - /// when used as a start parameter. - pub fn start_parameter(mut self, val: T) -> Self - where - T: Into, - { - self.start_parameter = val.into(); - self - } - - /// Three-letter ISO 4217 currency code, see [more on currencies]. - /// - /// [more on currencies]: https://core.telegram.org/bots/payments#supported-currencies - pub fn currency(mut self, val: T) -> Self - where - T: Into, - { - self.currency = val.into(); - self - } - - /// Price breakdown, a list of components (e.g. product price, tax, - /// discount, delivery cost, delivery tax, bonus, etc.). - pub fn prices(mut self, val: T) -> Self - where - T: Into>, - { - self.prices = val.into(); - self - } - - /// JSON-encoded data about the invoice, which will be shared with the - /// payment provider. - /// - /// A detailed description of required fields should be provided by the - /// payment provider. - pub fn provider_data(mut self, val: T) -> Self - where - T: Into, - { - self.provider_data = Some(val.into()); - self - } - - /// URL of the product photo for the invoice. - /// - /// Can be a photo of the goods or a marketing image for a service. People - /// like it better when they see what they are paying for. - pub fn photo_url(mut self, val: T) -> Self - where - T: Into, - { - self.photo_url = Some(val.into()); - self - } - - /// Photo size. - pub fn photo_size(mut self, val: i32) -> Self { - self.photo_size = Some(val); - self - } - - /// Photo width. - pub fn photo_width(mut self, val: i32) -> Self { - self.photo_width = Some(val); - self - } - - /// Photo height. - pub fn photo_height(mut self, val: i32) -> Self { - self.photo_height = Some(val); - self - } - - /// Pass `true`, if you require the user's full name to complete the order. - pub fn need_name(mut self, val: bool) -> Self { - self.need_name = Some(val); - self - } - - /// Pass `true`, if you require the user's phone number to complete the - /// order. - pub fn need_phone_number(mut self, val: bool) -> Self { - self.need_phone_number = Some(val); - self - } - - /// Pass `true`, if you require the user's email address to complete the - /// order. - pub fn need_email(mut self, val: bool) -> Self { - self.need_email = Some(val); - self - } - - /// Pass `true`, if you require the user's shipping address to complete the - /// order. - pub fn need_shipping_address(mut self, val: bool) -> Self { - self.need_shipping_address = Some(val); - self - } - - /// Pass `true`, if user's phone number should be sent to provider. - pub fn send_phone_number_to_provider(mut self, val: bool) -> Self { - self.send_phone_number_to_provider = Some(val); - self - } - - /// Pass `true`, if user's email address should be sent to provider. - pub fn send_email_to_provider(mut self, val: bool) -> Self { - self.send_email_to_provider = Some(val); - self - } - - /// Pass `true`, if the final price depends on the shipping method. - #[allow(clippy::wrong_self_convention)] - pub fn is_flexible(mut self, val: bool) -> Self { - self.is_flexible = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// If empty, one 'Pay `total price`' button will be shown. If not empty, - /// the first button must be a Pay button. - /// - /// [inlint keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_location.rs b/src/requests/all/send_location.rs deleted file mode 100644 index 691d9b9e..00000000 --- a/src/requests/all/send_location.rs +++ /dev/null @@ -1,110 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, Message, ReplyMarkup}, - Bot, -}; - -/// 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(Debug, Clone, Serialize)] -pub struct SendLocation { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub latitude: f32, - pub longitude: f32, - pub live_period: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SendLocation { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendLocation", &self).await - } -} - -impl SendLocation { - pub(crate) fn new(bot: Bot, chat_id: C, latitude: f32, longitude: f32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { - bot, - chat_id, - latitude, - longitude, - live_period: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Latitude of the location. - pub fn latitude(mut self, val: f32) -> Self { - self.latitude = val; - self - } - - /// Longitude of the location. - pub fn longitude(mut self, val: f32) -> Self { - self.longitude = val; - self - } - - /// Period in seconds for which the location will be updated (see [Live - /// Locations], should be between 60 and 86400). - /// - /// [Live Locations]: https://telegram.org/blog/live-locations - pub fn live_period(mut self, val: i64) -> Self { - self.live_period = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// If empty, one 'Pay `total price`' button will be shown. If not empty, - /// the first button must be a Pay button. - /// - /// [inlint keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_media_group.rs b/src/requests/all/send_media_group.rs deleted file mode 100644 index c9cfdb34..00000000 --- a/src/requests/all/send_media_group.rs +++ /dev/null @@ -1,78 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, InputMedia, Message}, - Bot, -}; - -/// Use this method to send a group of photos or videos as an album. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendmediagroup). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendMediaGroup { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub media: Vec, // TODO: InputMediaPhoto and InputMediaVideo - pub disable_notification: Option, - pub reply_to_message_id: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SendMediaGroup { - type Output = Vec; - - async fn send(&self) -> ResponseResult> { - net::request_multipart(self.bot.client(), self.bot.token(), "sendMediaGroup", self).await - } -} - -impl SendMediaGroup { - pub(crate) fn new(bot: Bot, chat_id: C, media: M) -> Self - where - C: Into, - M: Into>, - { - let chat_id = chat_id.into(); - let media = media.into(); - Self { bot, chat_id, media, disable_notification: None, reply_to_message_id: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// A JSON-serialized array describing photos and videos to be sent, must - /// include 2–10 items. - pub fn media(mut self, val: T) -> Self - where - T: Into>, - { - self.media = val.into(); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the messages are a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } -} diff --git a/src/requests/all/send_message.rs b/src/requests/all/send_message.rs deleted file mode 100644 index a1c9b3b0..00000000 --- a/src/requests/all/send_message.rs +++ /dev/null @@ -1,121 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send text messages. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendmessage). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendMessage { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub text: String, - pub parse_mode: Option, - pub disable_web_page_preview: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SendMessage { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendMessage", &self).await - } -} - -impl SendMessage { - pub(crate) fn new(bot: Bot, chat_id: C, text: T) -> Self - where - C: Into, - T: Into, - { - Self { - bot, - chat_id: chat_id.into(), - text: text.into(), - parse_mode: None, - disable_web_page_preview: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, value: T) -> Self - where - T: Into, - { - self.chat_id = value.into(); - self - } - - /// Text of the message to be sent. - pub fn text(mut self, value: T) -> Self - where - T: Into, - { - self.text = value.into(); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, value: ParseMode) -> Self { - self.parse_mode = Some(value); - self - } - - /// Disables link previews for links in this message. - pub fn disable_web_page_preview(mut self, value: bool) -> Self { - self.disable_web_page_preview = Some(value); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, value: bool) -> Self { - self.disable_notification = Some(value); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, value: i32) -> Self { - self.reply_to_message_id = Some(value); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, value: T) -> Self - where - T: Into, - { - self.reply_markup = Some(value.into()); - self - } -} diff --git a/src/requests/all/send_photo.rs b/src/requests/all/send_photo.rs deleted file mode 100644 index 7ae7c46d..00000000 --- a/src/requests/all/send_photo.rs +++ /dev/null @@ -1,127 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send photos. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendphoto). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendPhoto { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub photo: InputFile, - pub caption: Option, - pub parse_mode: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SendPhoto { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_multipart(self.bot.client(), self.bot.token(), "sendPhoto", self).await - } -} - -impl SendPhoto { - pub(crate) fn new(bot: Bot, chat_id: C, photo: InputFile) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - photo, - caption: None, - parse_mode: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Photo to send. - /// - /// Pass [`InputFile::File`] to send a photo that exists on - /// the Telegram servers (recommended), pass an [`InputFile::Url`] for - /// Telegram to get a .webp file from the Internet, or upload a new one - /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. - /// - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`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 photo(mut self, val: InputFile) -> Self { - self.photo = val; - self - } - - ///Photo caption (may also be used when resending photos by file_id), - /// 0-1024 characters. - pub fn caption(mut self, val: T) -> Self - where - T: Into, - { - self.caption = Some(val.into()); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. A JSON-serialized object for an [inline - /// keyboard], [custom reply keyboard], instructions to remove reply - /// keyboard or to force a reply from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_poll.rs b/src/requests/all/send_poll.rs deleted file mode 100644 index d5a538c4..00000000 --- a/src/requests/all/send_poll.rs +++ /dev/null @@ -1,217 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, Message, ParseMode, PollType, ReplyMarkup}, - Bot, -}; - -/// 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(Debug, Clone, Serialize)] -pub struct SendPoll { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub question: String, - pub options: Vec, - pub is_anonymous: Option, - pub poll_type: Option, - pub allows_multiple_answers: Option, - pub correct_option_id: Option, - pub explanation: Option, - pub explanation_parse_mode: Option, - pub open_period: Option, - pub close_date: Option, - pub is_closed: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SendPoll { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendPoll", &self).await - } -} - -impl SendPoll { - pub(crate) fn new(bot: Bot, chat_id: C, question: Q, options: O) -> Self - where - C: Into, - Q: Into, - O: Into>, - { - let chat_id = chat_id.into(); - let question = question.into(); - let options = options.into(); - Self { - bot, - chat_id, - question, - options, - is_anonymous: None, - poll_type: None, - allows_multiple_answers: None, - correct_option_id: None, - explanation: None, - explanation_parse_mode: None, - is_closed: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - close_date: None, - open_period: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - /// - /// A native poll can't be sent to a private chat. - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Poll question, 1-255 characters. - pub fn question(mut self, val: T) -> Self - where - T: Into, - { - self.question = val.into(); - self - } - - /// List of answer options, 2-10 strings 1-100 characters each. - pub fn options(mut self, val: T) -> Self - where - T: Into>, - { - self.options = val.into(); - self - } - - /// `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, - { - self.is_anonymous = Some(val.into()); - self - } - - /// Poll type, `quiz` or `regular`, defaults to `regular`. - pub fn poll_type(mut self, val: PollType) -> Self { - self.poll_type = Some(val); - self - } - - /// `true`, if the poll allows multiple answers, ignored for polls in quiz - /// mode. - /// - /// Defaults to `false`. - pub fn allows_multiple_answers(mut self, val: T) -> Self - where - T: Into, - { - self.allows_multiple_answers = Some(val.into()); - self - } - - /// 0-based identifier of the correct answer option, required for polls in - /// quiz mode. - pub fn correct_option_id(mut self, val: T) -> Self - where - T: Into, - { - self.correct_option_id = Some(val.into()); - self - } - - /// Text that is shown when a user chooses an incorrect answer or taps on - /// the lamp icon in a quiz-style poll, 0-200 characters with at most 2 line - /// feeds after entities parsing. - pub fn explanation(mut self, val: T) -> Self - where - T: Into, - { - self.explanation = Some(val.into()); - self - } - - /// Mode for parsing entities in the explanation. See [formatting options] - /// for more details. - /// - /// [formatting options]: https://core.telegram.org/bots/api#formatting-options - pub fn explanation_parse_mode(mut self, val: ParseMode) -> Self { - self.explanation_parse_mode = Some(val); - self - } - - /// Amount of time in seconds the poll will be active after creation, 5-600. - /// Can't be used together with `close_date`. - pub fn open_period(mut self, val: i32) -> Self { - self.open_period = Some(val); - self - } - - /// Point in time (Unix timestamp) when the poll will be automatically - /// closed. Must be at least 5 and no more than 600 seconds in the future. - /// Can't be used together with `open_period`. - pub fn close_date(mut self, val: i32) -> Self { - self.close_date = Some(val); - self - } - - /// 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, - { - self.is_closed = Some(val.into()); - self - } - - /// Sends the message [silently]. - /// - /// Users will receive a notification with no sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_sticker.rs b/src/requests/all/send_sticker.rs deleted file mode 100644 index 13bc7f0e..00000000 --- a/src/requests/all/send_sticker.rs +++ /dev/null @@ -1,104 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, InputFile, Message, ReplyMarkup}, - Bot, -}; - -/// 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 -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendSticker { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub sticker: InputFile, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SendSticker { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_multipart(self.bot.client(), self.bot.token(), "sendSticker", self).await - } -} - -impl SendSticker { - pub(crate) fn new(bot: Bot, chat_id: C, sticker: InputFile) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - sticker, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Sticker to send. - /// - /// Pass [`InputFile::File`] to send a file that exists on - /// the Telegram servers (recommended), pass an [`InputFile::Url`] for - /// Telegram to get a .webp file from the Internet, or upload a new one - /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. - /// - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`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 sticker(mut self, val: InputFile) -> Self { - self.sticker = val; - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_venue.rs b/src/requests/all/send_venue.rs deleted file mode 100644 index c2aab592..00000000 --- a/src/requests/all/send_venue.rs +++ /dev/null @@ -1,159 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, Message, ReplyMarkup}, - Bot, -}; - -/// 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(Debug, Clone, Serialize)] -pub struct SendVenue { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub latitude: f32, - pub longitude: f32, - pub title: String, - pub address: String, - pub foursquare_id: Option, - pub foursquare_type: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SendVenue { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendVenue", &self).await - } -} - -impl SendVenue { - pub(crate) fn new( - bot: Bot, - chat_id: C, - latitude: f32, - longitude: f32, - title: T, - address: A, - ) -> Self - where - C: Into, - T: Into, - A: Into, - { - let chat_id = chat_id.into(); - let title = title.into(); - let address = address.into(); - Self { - bot, - chat_id, - latitude, - longitude, - title, - address, - foursquare_id: None, - foursquare_type: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Latitude of the venue. - pub fn latitude(mut self, val: f32) -> Self { - self.latitude = val; - self - } - - /// Longitude of the venue. - pub fn longitude(mut self, val: f32) -> Self { - self.longitude = val; - self - } - - /// Name of the venue. - pub fn title(mut self, val: T) -> Self - where - T: Into, - { - self.title = val.into(); - self - } - - /// Address of the venue. - pub fn address(mut self, val: T) -> Self - where - T: Into, - { - self.address = val.into(); - self - } - - /// Foursquare identifier of the venue. - pub fn foursquare_id(mut self, val: T) -> Self - where - T: Into, - { - self.foursquare_id = Some(val.into()); - self - } - - /// Foursquare type of the venue, if known. - /// - /// For example, `arts_entertainment/default`, `arts_entertainment/aquarium` - /// or `food/icecream`. - pub fn foursquare_type(mut self, val: T) -> Self - where - T: Into, - { - self.foursquare_type = Some(val.into()); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_video.rs b/src/requests/all/send_video.rs deleted file mode 100644 index 9ee14b3f..00000000 --- a/src/requests/all/send_video.rs +++ /dev/null @@ -1,182 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send video files, Telegram clients support mp4 videos -/// (other formats may be sent as Document). -/// -/// Bots can currently send video files of up to 50 MB in size, this -/// limit may be changed in the future. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendvideo). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendVideo { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub video: InputFile, - pub duration: Option, - pub width: Option, - pub height: Option, - pub thumb: Option, - pub caption: Option, - pub parse_mode: Option, - pub supports_streaming: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SendVideo { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_multipart(self.bot.client(), self.bot.token(), "sendVideo", self).await - } -} - -impl SendVideo { - pub(crate) fn new(bot: Bot, chat_id: C, video: InputFile) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - video, - duration: None, - width: None, - height: None, - thumb: None, - caption: None, - parse_mode: None, - supports_streaming: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Video to sent. - /// - /// Pass [`InputFile::File`] to send a file that exists on - /// the Telegram servers (recommended), pass an [`InputFile::Url`] for - /// Telegram to get a .webp file from the Internet, or upload a new one - /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. - /// - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`InputFile::Url`]: crate::types::InputFile::Url - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - pub fn video(mut self, val: InputFile) -> Self { - self.video = val; - self - } - - /// Duration of sent video in seconds. - pub fn duration(mut self, val: i32) -> Self { - self.duration = Some(val); - self - } - - /// Video width. - pub fn width(mut self, val: i32) -> Self { - self.width = Some(val); - self - } - - /// Video height. - pub fn height(mut self, val: i32) -> Self { - self.height = Some(val); - self - } - - /// Thumbnail of the file sent; can be ignored if thumbnail generation for - /// the file is supported server-side. - /// - /// The thumbnail should be in JPEG format and less than 200 kB in size. A - /// thumbnail‘s width and height should not exceed 320. Ignored if the - /// file is not uploaded using `multipart/form-data`. Thumbnails can’t be - /// reused and can be only uploaded as a new file, so you can pass - /// `attach://` if the thumbnail was uploaded using - /// `multipart/form-data` under ``. [More info on Sending - /// Files Âģ]. - /// - /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - /// Video caption (may also be used when resending videos by file_id), - /// 0-1024 characters. - pub fn caption(mut self, val: T) -> Self - where - T: Into, - { - self.caption = Some(val.into()); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// Pass `true`, if the uploaded video is suitable for streaming. - pub fn supports_streaming(mut self, val: bool) -> Self { - self.supports_streaming = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_video_note.rs b/src/requests/all/send_video_note.rs deleted file mode 100644 index d0ff8485..00000000 --- a/src/requests/all/send_video_note.rs +++ /dev/null @@ -1,138 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, InputFile, Message, ReplyMarkup}, - Bot, -}; - -/// 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. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendvideonote). -/// -/// [v.4.0]: https://telegram.org/blog/video-messages-and-telescope -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendVideoNote { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub video_note: InputFile, - pub duration: Option, - pub length: Option, - pub thumb: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SendVideoNote { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_multipart(self.bot.client(), self.bot.token(), "sendVideoNote", self).await - } -} - -impl SendVideoNote { - pub(crate) fn new(bot: Bot, chat_id: C, video_note: InputFile) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - video_note, - duration: None, - length: None, - thumb: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Video note to send. - /// - /// Pass [`InputFile::File`] to send a file that exists on - /// the Telegram servers (recommended), pass an [`InputFile::Url`] for - /// Telegram to get a .webp file from the Internet, or upload a new one - /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. - /// - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`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 video_note(mut self, val: InputFile) -> Self { - self.video_note = val; - self - } - - /// Duration of sent video in seconds. - pub fn duration(mut self, val: i32) -> Self { - self.duration = Some(val); - self - } - - /// Video width and height, i.e. diameter of the video message. - pub fn length(mut self, val: i32) -> Self { - self.length = Some(val); - self - } - - /// Thumbnail of the file sent; can be ignored if thumbnail generation for - /// the file is supported server-side. - /// - /// The thumbnail should be in JPEG format and less than 200 kB in size. A - /// thumbnail‘s width and height should not exceed 320. Ignored if the - /// file is not uploaded using `multipart/form-data`. Thumbnails can’t - /// be reused and can be only uploaded as a new file, so you can pass - /// `attach://` if the thumbnail was uploaded using - /// `multipart/form-data` under ``. [More info on - /// Sending Files Âģ]. - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_voice.rs b/src/requests/all/send_voice.rs deleted file mode 100644 index bb59c36c..00000000 --- a/src/requests/all/send_voice.rs +++ /dev/null @@ -1,144 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send audio files, if you want Telegram clients to display -/// the file as a playable voice message. -/// -/// For this to work, your audio must be in an .ogg file encoded with OPUS -/// (other formats may be sent as [`Audio`] or [`Document`]). Bots can currently -/// send voice messages of up to 50 MB in size, this limit may be changed in the -/// future. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendvoice). -/// -/// [`Audio`]: crate::types::Audio -/// [`Document`]: crate::types::Document -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendVoice { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub voice: InputFile, - pub caption: Option, - pub parse_mode: Option, - pub duration: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SendVoice { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_multipart(self.bot.client(), self.bot.token(), "sendVoice", self).await - } -} - -impl SendVoice { - pub(crate) fn new(bot: Bot, chat_id: C, voice: InputFile) -> Self - where - C: Into, - { - Self { - bot, - chat_id: chat_id.into(), - voice, - caption: None, - parse_mode: None, - duration: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Audio file to send. - /// - /// Pass [`InputFile::File`] to send a file that exists on - /// the Telegram servers (recommended), pass an [`InputFile::Url`] for - /// Telegram to get a .webp file from the Internet, or upload a new one - /// using [`InputFile::FileId`]. [More info on Sending Files Âģ]. - /// - /// [`InputFile::File`]: crate::types::InputFile::File - /// [`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 voice(mut self, val: InputFile) -> Self { - self.voice = val; - self - } - - /// Voice message caption, 0-1024 characters. - pub fn caption(mut self, val: T) -> Self - where - T: Into, - { - self.caption = Some(val.into()); - self - } - - /// Send [Markdown] or [HTML], if you want Telegram apps to show - /// [bold, italic, fixed-width text or inline URLs] in the media caption. - /// - /// [Markdown]: crate::types::ParseMode::Markdown - /// [HTML]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: - /// crate::types::ParseMode - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - /// Duration of the voice message in seconds. - pub fn duration(mut self, val: i32) -> Self { - self.duration = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/set_chat_administrator_custom_title.rs b/src/requests/all/set_chat_administrator_custom_title.rs deleted file mode 100644 index 95fbbe9a..00000000 --- a/src/requests/all/set_chat_administrator_custom_title.rs +++ /dev/null @@ -1,75 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// 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(Debug, Clone, Serialize)] -pub struct SetChatAdministratorCustomTitle { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub user_id: i32, - pub custom_title: String, -} - -#[async_trait::async_trait] -impl RequestOld for SetChatAdministratorCustomTitle { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json( - self.bot.client(), - self.bot.token(), - "setChatAdministratorCustomTitle", - &self, - ) - .await - } -} - -impl SetChatAdministratorCustomTitle { - pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32, custom_title: CT) -> Self - where - C: Into, - CT: Into, - { - let chat_id = chat_id.into(); - let custom_title = custom_title.into(); - Self { bot, chat_id, user_id, custom_title } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// New custom title for the administrator; 0-16 characters, emoji are not - /// allowed. - pub fn custom_title(mut self, val: T) -> Self - where - T: Into, - { - self.custom_title = val.into(); - self - } -} diff --git a/src/requests/all/set_chat_description.rs b/src/requests/all/set_chat_description.rs deleted file mode 100644 index a36e06e4..00000000 --- a/src/requests/all/set_chat_description.rs +++ /dev/null @@ -1,62 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to change the description of a group, a supergroup or a -/// channel. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#setchatdescription). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetChatDescription { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub description: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SetChatDescription { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setChatDescription", &self).await - } -} - -impl SetChatDescription { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, description: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// New chat description, 0-255 characters. - pub fn description(mut self, val: T) -> Self - where - T: Into, - { - self.description = Some(val.into()); - self - } -} diff --git a/src/requests/all/set_chat_permissions.rs b/src/requests/all/set_chat_permissions.rs deleted file mode 100644 index 2d14f6c7..00000000 --- a/src/requests/all/set_chat_permissions.rs +++ /dev/null @@ -1,58 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, ChatPermissions, True}, - Bot, -}; - -/// Use this method to set default chat permissions for all members. -/// -/// The bot must be an administrator in the group or a supergroup for this to -/// work and must have the can_restrict_members admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#setchatpermissions). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetChatPermissions { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub permissions: ChatPermissions, -} - -#[async_trait::async_trait] -impl RequestOld for SetChatPermissions { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "sendChatPermissions", &self).await - } -} - -impl SetChatPermissions { - pub(crate) fn new(bot: Bot, chat_id: C, permissions: ChatPermissions) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, permissions } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup (in the format `@supergroupusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// New default chat permissions. - pub fn permissions(mut self, val: ChatPermissions) -> Self { - self.permissions = val; - self - } -} diff --git a/src/requests/all/set_chat_photo.rs b/src/requests/all/set_chat_photo.rs deleted file mode 100644 index 1df5c413..00000000 --- a/src/requests/all/set_chat_photo.rs +++ /dev/null @@ -1,58 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, InputFile, True}, - Bot, -}; - -/// Use this method to set a new profile photo for the chat. -/// -/// Photos can't be changed for private chats. The bot must be an administrator -/// in the chat for this to work and must have the appropriate admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#setchatphoto). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetChatPhoto { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub photo: InputFile, -} - -#[async_trait::async_trait] -impl RequestOld for SetChatPhoto { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setChatPhoto", &self).await - } -} - -impl SetChatPhoto { - pub(crate) fn new(bot: Bot, chat_id: C, photo: InputFile) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, photo } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// New chat photo, uploaded using `multipart/form-data`. - pub fn photo(mut self, val: InputFile) -> Self { - self.photo = val; - self - } -} diff --git a/src/requests/all/set_chat_sticker_set.rs b/src/requests/all/set_chat_sticker_set.rs deleted file mode 100644 index 2e24bac2..00000000 --- a/src/requests/all/set_chat_sticker_set.rs +++ /dev/null @@ -1,64 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to set a new group sticker set for a supergroup. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. Use the field can_set_sticker_set optionally -/// returned in getChat requests to check if the bot can use this method. -/// -/// [The official docs](https://core.telegram.org/bots/api#setchatstickerset). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetChatStickerSet { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub sticker_set_name: String, -} - -#[async_trait::async_trait] -impl RequestOld for SetChatStickerSet { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setChatStickerSet", &self).await - } -} - -impl SetChatStickerSet { - pub(crate) fn new(bot: Bot, chat_id: C, sticker_set_name: S) -> Self - where - C: Into, - S: Into, - { - let chat_id = chat_id.into(); - let sticker_set_name = sticker_set_name.into(); - Self { bot, chat_id, sticker_set_name } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup (in the format `@supergroupusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Name of the sticker set to be set as the group sticker set. - pub fn sticker_set_name(mut self, val: T) -> Self - where - T: Into, - { - self.sticker_set_name = val.into(); - self - } -} diff --git a/src/requests/all/set_chat_title.rs b/src/requests/all/set_chat_title.rs deleted file mode 100644 index d0566a7e..00000000 --- a/src/requests/all/set_chat_title.rs +++ /dev/null @@ -1,63 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to change the title of a chat. -/// -/// Titles can't be changed for private chats. The bot must be an administrator -/// in the chat for this to work and must have the appropriate admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#setchattitle). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetChatTitle { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub title: String, -} - -#[async_trait::async_trait] -impl RequestOld for SetChatTitle { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setChatTitle", &self).await - } -} - -impl SetChatTitle { - pub(crate) fn new(bot: Bot, chat_id: C, title: T) -> Self - where - C: Into, - T: Into, - { - let chat_id = chat_id.into(); - let title = title.into(); - Self { bot, chat_id, title } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// New chat title, 1-255 characters. - pub fn title(mut self, val: T) -> Self - where - T: Into, - { - self.title = val.into(); - self - } -} diff --git a/src/requests/all/set_game_score.rs b/src/requests/all/set_game_score.rs deleted file mode 100644 index 43ecc9d6..00000000 --- a/src/requests/all/set_game_score.rs +++ /dev/null @@ -1,89 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{Message, TargetMessage}, - Bot, -}; - -/// Use this method to set the score of the specified user in a game. -/// -/// On success, if the message was sent by the bot, returns the edited -/// [`Message`], otherwise returns [`True`]. Returns an error, if the new score -/// is not greater than the user's current score in the chat and force is -/// `false`. -/// -/// [The official docs](https://core.telegram.org/bots/api#setgamescore). -/// -/// [`Message`]: crate::types::Message -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetGameScore { - #[serde(skip_serializing)] - bot: Bot, - #[serde(flatten)] - pub target: TargetMessage, - pub user_id: i32, - pub score: i32, - pub force: Option, - pub disable_edit_message: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SetGameScore { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setGameScore", &self).await - } -} - -impl SetGameScore { - pub(crate) fn new(bot: Bot, target: T, user_id: i32, score: i32) -> Self - where - T: Into, - { - let target = target.into(); - Self { bot, target, user_id, score, force: None, disable_edit_message: None } - } - - /// Target message, either chat id and message id or inline message id. - pub fn target(mut self, val: T) -> Self - where - T: Into, - { - self.target = val.into(); - self - } - - /// User identifier. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// New score, must be non-negative. - pub fn score(mut self, val: i32) -> Self { - self.score = val; - self - } - - /// Pass `true`, if the high score is allowed to decrease. - /// - /// This can be useful when fixing mistakes or banning cheaters. - pub fn force(mut self, val: bool) -> Self { - self.force = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_edit_message(mut self, val: bool) -> Self { - self.disable_edit_message = Some(val); - self - } -} diff --git a/src/requests/all/set_my_commands.rs b/src/requests/all/set_my_commands.rs deleted file mode 100644 index 3e06920f..00000000 --- a/src/requests/all/set_my_commands.rs +++ /dev/null @@ -1,49 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{BotCommand, True}, - Bot, -}; - -/// Use this method to change the list of the bot's commands. -/// -/// [The official docs](https://core.telegram.org/bots/api#setmycommands). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetMyCommands { - #[serde(skip_serializing)] - bot: Bot, - pub commands: Vec, -} - -#[async_trait::async_trait] -impl RequestOld for SetMyCommands { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setMyCommands", &self).await - } -} - -impl SetMyCommands { - pub(crate) fn new(bot: Bot, commands: C) -> Self - where - C: Into>, - { - Self { bot, commands: commands.into() } - } - - /// A JSON-serialized list of bot commands to be set as the list of the - /// bot's commands. - /// - /// At most 100 commands can be specified. - pub fn commands(mut self, commands: C) -> Self - where - C: Into>, - { - self.commands = commands.into(); - self - } -} diff --git a/src/requests/all/set_sticker_position_in_set.rs b/src/requests/all/set_sticker_position_in_set.rs deleted file mode 100644 index bb752b1f..00000000 --- a/src/requests/all/set_sticker_position_in_set.rs +++ /dev/null @@ -1,56 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::True, - Bot, -}; - -/// 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(Debug, Clone, Serialize)] -pub struct SetStickerPositionInSet { - #[serde(skip_serializing)] - bot: Bot, - pub sticker: String, - pub position: i32, -} - -#[async_trait::async_trait] -impl RequestOld for SetStickerPositionInSet { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setStickerPositionInSet", &self) - .await - } -} - -impl SetStickerPositionInSet { - pub(crate) fn new(bot: Bot, sticker: S, position: i32) -> Self - where - S: Into, - { - let sticker = sticker.into(); - Self { bot, sticker, position } - } - - /// File identifier of the sticker. - pub fn sticker(mut self, val: T) -> Self - where - T: Into, - { - self.sticker = val.into(); - self - } - - /// New sticker position in the set, zero-based. - pub fn position(mut self, val: i32) -> Self { - self.position = val; - self - } -} diff --git a/src/requests/all/set_sticker_set_thumb.rs b/src/requests/all/set_sticker_set_thumb.rs deleted file mode 100644 index 745805d6..00000000 --- a/src/requests/all/set_sticker_set_thumb.rs +++ /dev/null @@ -1,73 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{InputFile, True}, - Bot, -}; - -/// Use this method to set the thumbnail of a sticker set. Animated thumbnails -/// can be set for animated sticker sets only. -/// -/// [The official docs](https://core.telegram.org/bots/api#setstickersetthumb). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetStickerSetThumb { - #[serde(skip_serializing)] - bot: Bot, - pub name: String, - pub user_id: i32, - pub thumb: Option, -} - -#[async_trait::async_trait] -impl RequestOld for SetStickerSetThumb { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setStickerSetThumb", &self).await - } -} - -impl SetStickerSetThumb { - pub(crate) fn new(bot: Bot, name: S, user_id: i32) -> Self - where - S: Into, - { - Self { bot, name: name.into(), user_id, thumb: None } - } - - /// Sticker set name. - pub fn name(mut self, val: T) -> Self - where - T: Into, - { - self.name = val.into(); - self - } - - /// User identifier of the sticker set owner. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// A PNG image with the thumbnail, must be up to 128 kilobytes in size and - /// have width and height exactly 100px, or a TGS animation with the - /// thumbnail up to 32 kilobytes in size; see https://core.telegram.org/animated_stickers#technical-requirements - /// for animated sticker technical requirements. - /// - /// Pass [`InputFile::FileId`] as a String to send a file that already - /// exists on the Telegram servers, pass [`InputFile::Url`] for Telegram - /// to get a file from the Internet, or upload a new one using - /// multipart/form-data. More info on Sending Files Âģ. Animated sticker - /// set thumbnail can't be uploaded via HTTP URL. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url]: crate::types::InputFile::Url - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } -} diff --git a/src/requests/all/set_webhook.rs b/src/requests/all/set_webhook.rs deleted file mode 100644 index f8d45282..00000000 --- a/src/requests/all/set_webhook.rs +++ /dev/null @@ -1,114 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{AllowedUpdate, InputFile, True}, - Bot, -}; - -/// Use this method to specify a url and receive incoming updates via an -/// outgoing webhook. -/// -/// Whenever there is an update for the bot, we will send an -/// HTTPS POST request to the specified url, containing a JSON-serialized -/// [`Update`]. In case of an unsuccessful request, we will give up after a -/// reasonable amount of attempts. -/// -/// If you'd like to make sure that the Webhook request comes from Telegram, -/// we recommend using a secret path in the URL, e.g. -/// `https://www.example.com/`. Since nobody else knows your bot‘s -/// token, you can be pretty sure it’s us. -/// -/// [The official docs](https://core.telegram.org/bots/api#setwebhook). -/// -/// [`Update`]: crate::types::Update -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetWebhook { - #[serde(skip_serializing)] - bot: Bot, - pub url: String, - pub certificate: Option, - pub max_connections: Option, - pub allowed_updates: Option>, -} - -#[async_trait::async_trait] -impl RequestOld for SetWebhook { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "setWebhook", &self).await - } -} - -impl SetWebhook { - pub(crate) fn new(bot: Bot, url: U) -> Self - where - U: Into, - { - let url = url.into(); - Self { bot, url, certificate: None, max_connections: None, allowed_updates: None } - } - - /// HTTPS url to send updates to. - /// - /// Use an empty string to remove webhook integration. - pub fn url(mut self, val: T) -> Self - where - T: Into, - { - self.url = val.into(); - self - } - - /// Upload your public key certificate so that the root certificate in use - /// can be checked. - /// - /// See our [self-signed guide] for details. - /// - /// [self-signed guide]: https://core.telegram.org/bots/self-signed - pub fn certificate(mut self, val: InputFile) -> Self { - self.certificate = Some(val); - self - } - - /// Maximum allowed number of simultaneous HTTPS connections to the webhook - /// for update delivery, 1-100. - /// - /// Defaults to 40. Use lower values to limit the load on your bot‘s server, - /// and higher values to increase your bot’s throughput. - pub fn max_connections(mut self, val: i32) -> Self { - self.max_connections = Some(val); - self - } - - /// List the types of updates you want your bot to receive. - /// - /// For example, specify [`AllowedUpdate::Message`], - /// [`AllowedUpdate::EditedChannelPost`], [`AllowedUpdate::CallbackQuery`] - /// to only receive updates of these types. Specify an empty list to receive - /// all updates regardless of type (default). If not specified, the - /// previous setting will be used. See [`AllowedUpdate`] for a complete list - /// of available update types. - /// - /// Please note that this parameter doesn't affect updates created before - /// the call to the [`Bot::set_webhook`], so unwanted updates may be - /// received for a short period of time. - /// - /// [`Bot::set_webhook`]: crate::Bot::set_webhook - /// [`AllowedUpdate::Message`]: crate::types::AllowedUpdate::Message - /// [`AllowedUpdate::EditedChannelPost`]: - /// crate::types::AllowedUpdate::EditedChannelPost - /// [`AllowedUpdate::CallbackQuery`]: - /// crate::types::AllowedUpdate::CallbackQuery - /// [`AllowedUpdate`]: crate::types::AllowedUpdate - pub fn allowed_updates(mut self, val: T) -> Self - where - T: Into>, - { - self.allowed_updates = Some(val.into()); - self - } -} diff --git a/src/requests/all/stop_inline_message_live_location.rs b/src/requests/all/stop_inline_message_live_location.rs deleted file mode 100644 index 38669a63..00000000 --- a/src/requests/all/stop_inline_message_live_location.rs +++ /dev/null @@ -1,62 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{InlineKeyboardMarkup, Message}, - Bot, -}; - -/// Use this method to stop updating a live location message (sent via the bot) -/// before `live_period` expires. -/// -/// On success, [`True`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation). -/// -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct StopInlineMessageLiveLocation { - #[serde(skip_serializing)] - bot: Bot, - pub inline_message_id: String, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for StopInlineMessageLiveLocation { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "stopMessageLiveLocation", &self) - .await - } -} - -impl StopInlineMessageLiveLocation { - pub(crate) fn new(bot: Bot, inline_message_id: I) -> Self - where - I: Into, - { - let inline_message_id = inline_message_id.into(); - Self { bot, inline_message_id, reply_markup: None } - } - - /// Identifier of the inline message. - pub fn inline_message_id(mut self, val: T) -> Self - where - T: Into, - { - self.inline_message_id = val.into(); - self - } - - /// A JSON-serialized object for a new [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/stop_message_live_location.rs b/src/requests/all/stop_message_live_location.rs deleted file mode 100644 index 71400dc2..00000000 --- a/src/requests/all/stop_message_live_location.rs +++ /dev/null @@ -1,70 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, InlineKeyboardMarkup, Message}, - Bot, -}; - -/// Use this method to stop updating a live location message before -/// `live_period` expires. -/// -/// On success, the sent [`Message`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation). -/// -/// [`Message`]: crate::types::Message -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct StopMessageLiveLocation { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub message_id: i32, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for StopMessageLiveLocation { - type Output = Message; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "stopMessageLiveLocation", &self) - .await - } -} - -impl StopMessageLiveLocation { - pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id, reply_markup: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`) - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of the message to edit - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - /// A JSON-serialized object for a new [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/stop_poll.rs b/src/requests/all/stop_poll.rs deleted file mode 100644 index 14252739..00000000 --- a/src/requests/all/stop_poll.rs +++ /dev/null @@ -1,66 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, InlineKeyboardMarkup, Poll}, - Bot, -}; - -/// 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(Debug, Clone, Serialize)] -pub struct StopPoll { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub message_id: i32, - pub reply_markup: Option, -} - -#[async_trait::async_trait] -impl RequestOld for StopPoll { - type Output = Poll; - - /// On success, the stopped [`Poll`] with the final results is returned. - /// - /// [`Poll`]: crate::types::Poll - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "stopPoll", &self).await - } -} -impl StopPoll { - pub(crate) fn new(bot: Bot, chat_id: C, message_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id, reply_markup: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Identifier of the original message with the poll. - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - /// A JSON-serialized object for a new [inline keyboard]. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/unban_chat_member.rs b/src/requests/all/unban_chat_member.rs deleted file mode 100644 index f0bfbe3f..00000000 --- a/src/requests/all/unban_chat_member.rs +++ /dev/null @@ -1,58 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// 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, -/// but will be able to join via link, etc. The bot must be an administrator for -/// this to work. -/// -/// [The official docs](https://core.telegram.org/bots/api#unbanchatmember). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct UnbanChatMember { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub user_id: i32, -} - -#[async_trait::async_trait] -impl RequestOld for UnbanChatMember { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "unbanChatMember", &self).await - } -} - -impl UnbanChatMember { - pub(crate) fn new(bot: Bot, chat_id: C, user_id: i32) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, user_id } - } - - /// Unique identifier for the target group or username of the target - /// supergroup or channel (in the format `@username`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } -} diff --git a/src/requests/all/unpin_chat_message.rs b/src/requests/all/unpin_chat_message.rs deleted file mode 100644 index eab4dd8a..00000000 --- a/src/requests/all/unpin_chat_message.rs +++ /dev/null @@ -1,52 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to unpin a message in a group, a supergroup, or a channel. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the `can_pin_messages` admin right in the supergroup or `can_edit_messages` -/// admin right in the channel. -/// -/// [The official docs](https://core.telegram.org/bots/api#unpinchatmessage). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct UnpinChatMessage { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, -} - -#[async_trait::async_trait] -impl RequestOld for UnpinChatMessage { - type Output = True; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "unpinChatMessage", &self).await - } -} - -impl UnpinChatMessage { - pub(crate) fn new(bot: Bot, chat_id: C) -> Self - where - C: Into, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id(mut self, val: T) -> Self - where - T: Into, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/upload_sticker_file.rs b/src/requests/all/upload_sticker_file.rs deleted file mode 100644 index 73ff5f87..00000000 --- a/src/requests/all/upload_sticker_file.rs +++ /dev/null @@ -1,55 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{RequestOld, ResponseResult}, - types::{File, InputFile}, - Bot, -}; - -/// 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 -/// be used multiple times). -/// -/// [The official docs](https://core.telegram.org/bots/api#uploadstickerfile). -/// -/// [`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(Debug, Clone, Serialize)] -pub struct UploadStickerFile { - #[serde(skip_serializing)] - bot: Bot, - pub user_id: i32, - pub png_sticker: InputFile, -} -#[async_trait::async_trait] -impl RequestOld for UploadStickerFile { - type Output = File; - - async fn send(&self) -> ResponseResult { - net::request_json(self.bot.client(), self.bot.token(), "uploadStickerFile", &self).await - } -} - -impl UploadStickerFile { - pub(crate) fn new(bot: Bot, user_id: i32, png_sticker: InputFile) -> Self { - Self { bot, user_id, png_sticker } - } - - /// User identifier of sticker file owner. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// **Png** image with the sticker, must be up to 512 kilobytes in size, - /// dimensions must not exceed 512px, and either width or height must be - /// exactly 512px. [More info on Sending Files Âģ]. - /// - /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files - pub fn png_sticker(mut self, val: InputFile) -> Self { - self.png_sticker = val; - self - } -} diff --git a/src/requests/mod.rs b/src/requests/mod.rs index cc134a48..3433c81e 100644 --- a/src/requests/mod.rs +++ b/src/requests/mod.rs @@ -1,23 +1,9 @@ //! Telegram API requests. -mod has_payload; -mod payload; -mod request; - -pub use self::{has_payload::HasPayload, payload::Payload, request::Request}; - -mod all; -mod json; -mod multipart; -mod requester; -mod requester_ext; -mod utils; - -pub use all::*; -pub use json::JsonRequest; -pub use multipart::MultipartRequest; -pub use requester::Requester; -pub use requester_ext::RequesterExt; +pub use self::{ + has_payload::HasPayload, json::JsonRequest, multipart::MultipartRequest, payload::Payload, + request::Request, requester::Requester, requester_ext::RequesterExt, +}; /// A type that is returned after making a request to Telegram. pub type ResponseResult = Result; @@ -26,12 +12,11 @@ pub type ResponseResult = Result; /// `<::Payload as Payload>::Output`. pub type Output = <::Payload as Payload>::Output; -/// Designates an API request. -#[async_trait::async_trait] -pub trait RequestOld { - /// A data structure returned if success. - type Output; - - /// Asynchronously sends this request to Telegram and returns the result. - async fn send(&self) -> ResponseResult; -} +mod has_payload; +mod json; +mod multipart; +mod payload; +mod request; +mod requester; +mod requester_ext; +mod utils; From 1882ff0bc44f7ef033ad40bc6823563a26847fe3 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 27 Nov 2020 01:27:51 +0300 Subject: [PATCH 159/755] Fix doc test --- src/bot/download.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/bot/download.rs b/src/bot/download.rs index eed32b1c..aff200c2 100644 --- a/src/bot/download.rs +++ b/src/bot/download.rs @@ -17,16 +17,18 @@ impl Bot { /// ## Examples /// /// ```no_run - /// use teloxide_core::types::File as TgFile; + /// use teloxide_core::{ + /// requests::{Request, Requester}, + /// types::File as TgFile, + /// Bot, + /// }; /// use tokio::fs::File; - /// # use teloxide_core::RequestError; - /// use teloxide_core::{requests::RequestOld, Bot}; /// /// # async fn run() -> Result<(), Box> { /// let bot = Bot::new("TOKEN"); - /// let mut file = File::create("/home/waffle/Pictures/test.png").await?; /// /// let TgFile { file_path, .. } = bot.get_file("*file_id*").send().await?; + /// let mut file = File::create("/home/waffle/Pictures/test.png").await?; /// bot.download_file(&file_path, &mut file).await?; /// # Ok(()) } /// ``` From 3491a668b2d24e8dcde9d376e9fa25b74654187a Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 27 Nov 2020 02:13:50 +0300 Subject: [PATCH 160/755] Fix doc links... --- src/bot/download.rs | 8 ++-- src/errors.rs | 96 ++++++++++++++++++++++----------------------- src/types/chat.rs | 42 ++++++++++---------- src/types/file.rs | 4 +- src/types/game.rs | 11 +++--- src/types/me.rs | 4 +- src/types/update.rs | 4 +- 7 files changed, 84 insertions(+), 85 deletions(-) diff --git a/src/bot/download.rs b/src/bot/download.rs index aff200c2..bc859df7 100644 --- a/src/bot/download.rs +++ b/src/bot/download.rs @@ -10,7 +10,7 @@ use crate::{ impl Bot { /// Download a file from Telegram into `destination`. /// - /// `path` can be obtained from [`Bot::get_file`]. + /// `path` can be obtained from [`GetFile`]. /// /// To download as a stream of chunks, see [`Bot::download_file_stream`]. /// @@ -33,7 +33,7 @@ impl Bot { /// # Ok(()) } /// ``` /// - /// [`Bot::get_file`]: crate::Bot::get_file + /// [`GetFile`]: crate::payloads::GetFile /// [`Bot::download_file_stream`]: crate::Bot::download_file_stream pub async fn download_file( &self, @@ -48,12 +48,12 @@ impl Bot { /// Download a file from Telegram. /// - /// `path` can be obtained from the [`Bot::get_file`]. + /// `path` can be obtained from the [`GetFile`]. /// /// To download into [`AsyncWrite`] (e.g. [`tokio::fs::File`]), see /// [`Bot::download_file`]. /// - /// [`Bot::get_file`]: crate::bot::Bot::get_file + /// [`GetFile`]: crate::payloads::GetFile /// [`AsyncWrite`]: tokio::io::AsyncWrite /// [`tokio::fs::File`]: tokio::fs::File /// [`Bot::download_file`]: crate::Bot::download_file diff --git a/src/errors.rs b/src/errors.rs index d12296ae..1164398a 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -70,7 +70,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`EditMessageText`] /// - /// [`EditMessageText`]: crate::requests::EditMessageText + /// [`EditMessageText`]: crate::payloads::EditMessageText #[serde(rename = "Bad Request: message is not modified: specified new message content and \ reply markup are exactly the same as a current content and reply markup \ of the message")] @@ -82,8 +82,8 @@ pub enum ApiError { /// 1. [`ForwardMessage`] /// 2. [`DeleteMessage`] /// - /// [`ForwardMessage`]: crate::requests::ForwardMessage - /// [`DeleteMessage`]: crate::requests::DeleteMessage + /// [`ForwardMessage`]: crate::payloads::ForwardMessage + /// [`DeleteMessage`]: crate::payloads::DeleteMessage #[serde(rename = "Bad Request: MESSAGE_ID_INVALID")] MessageIdInvalid, @@ -92,7 +92,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`ForwardMessage`] /// - /// [`ForwardMessage`]: crate::requests::ForwardMessage + /// [`ForwardMessage`]: crate::payloads::ForwardMessage #[serde(rename = "Bad Request: message to forward not found")] MessageToForwardNotFound, @@ -101,7 +101,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`DeleteMessage`] /// - /// [`DeleteMessage`]: crate::requests::DeleteMessage + /// [`DeleteMessage`]: crate::payloads::DeleteMessage #[serde(rename = "Bad Request: message to delete not found")] MessageToDeleteNotFound, @@ -110,7 +110,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::SendMessage + /// [`SendMessage`]: crate::payloads::SendMessage #[serde(rename = "Bad Request: message text is empty")] MessageTextIsEmpty, @@ -119,7 +119,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`EditMessageText`] /// - /// [`EditMessageText`]: crate::requests::EditMessageText + /// [`EditMessageText`]: crate::payloads::EditMessageText #[serde(rename = "Bad Request: message can't be edited")] MessageCantBeEdited, @@ -129,7 +129,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`DeleteMessage`] /// - /// [`DeleteMessage`]: crate::requests::DeleteMessage + /// [`DeleteMessage`]: crate::payloads::DeleteMessage #[serde(rename = "Bad Request: message can't be deleted")] MessageCantBeDeleted, @@ -138,7 +138,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`EditMessageText`] /// - /// [`EditMessageText`]: crate::requests::EditMessageText + /// [`EditMessageText`]: crate::payloads::EditMessageText #[serde(rename = "Bad Request: message to edit not found")] MessageToEditNotFound, @@ -147,7 +147,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::SendMessage + /// [`SendMessage`]: crate::payloads::SendMessage #[serde(rename = "Bad Request: reply message not found")] MessageToReplyNotFound, @@ -161,7 +161,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::SendMessage + /// [`SendMessage`]: crate::payloads::SendMessage #[serde(rename = "Bad Request: message is too long")] MessageIsTooLong, @@ -170,7 +170,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendMediaGroup`] /// - /// [`SendMediaGroup`]: crate::requests::SendMediaGroup + /// [`SendMediaGroup`]: crate::payloads::SendMediaGroup #[serde(rename = "Bad Request: Too much messages to send as an album")] ToMuchMessages, @@ -179,7 +179,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendPoll`] /// - /// [`SendPoll`]: crate::requests::SendPoll + /// [`SendPoll`]: crate::payloads::SendPoll #[serde(rename = "Bad Request: poll has already been closed")] PollHasAlreadyClosed, @@ -188,7 +188,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendPoll`] /// - /// [`SendPoll`]: crate::requests::SendPoll + /// [`SendPoll`]: crate::payloads::SendPoll #[serde(rename = "Bad Request: poll must have at least 2 option")] PollMustHaveMoreOptions, @@ -197,7 +197,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendPoll`] /// - /// [`SendPoll`]: crate::requests::SendPoll + /// [`SendPoll`]: crate::payloads::SendPoll #[serde(rename = "Bad Request: poll can't have more than 10 options")] PollCantHaveMoreOptions, @@ -206,7 +206,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendPoll`] /// - /// [`SendPoll`]: crate::requests::SendPoll + /// [`SendPoll`]: crate::payloads::SendPoll #[serde(rename = "Bad Request: poll options must be non-empty")] PollOptionsMustBeNonEmpty, @@ -215,7 +215,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendPoll`] /// - /// [`SendPoll`]: crate::requests::SendPoll + /// [`SendPoll`]: crate::payloads::SendPoll #[serde(rename = "Bad Request: poll question must be non-empty")] PollQuestionMustBeNonEmpty, @@ -225,7 +225,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendPoll`] /// - /// [`SendPoll`]: crate::requests::SendPoll + /// [`SendPoll`]: crate::payloads::SendPoll #[serde(rename = "Bad Request: poll options length must not exceed 100")] PollOptionsLengthTooLong, @@ -235,7 +235,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendPoll`] /// - /// [`SendPoll`]: crate::requests::SendPoll + /// [`SendPoll`]: crate::payloads::SendPoll #[serde(rename = "Bad Request: poll question length must not exceed 255")] PollQuestionLengthTooLong, @@ -244,7 +244,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`StopPoll`] /// - /// [`StopPoll`]: crate::requests::StopPoll + /// [`StopPoll`]: crate::payloads::StopPoll #[serde(rename = "Bad Request: message with poll to stop not found")] MessageWithPollNotFound, @@ -253,7 +253,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`StopPoll`] /// - /// [`StopPoll`]: crate::requests::StopPoll + /// [`StopPoll`]: crate::payloads::StopPoll #[serde(rename = "Bad Request: message is not a poll")] MessageIsNotAPoll, @@ -263,7 +263,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::SendMessage + /// [`SendMessage`]: crate::payloads::SendMessage #[serde(rename = "Bad Request: chat not found")] ChatNotFound, @@ -273,7 +273,7 @@ pub enum ApiError { /// 1. [`getUserProfilePhotos`] /// /// [`getUserProfilePhotos`]: - /// crate::requests::GetUserProfilePhotos + /// crate::payloads::GetUserProfilePhotos #[serde(rename = "Bad Request: user not found")] UserNotFound, @@ -283,7 +283,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SetChatDescription`] /// - /// [`SetChatDescription`]: crate::requests::SetChatDescription + /// [`SetChatDescription`]: crate::payloads::SetChatDescription #[serde(rename = "Bad Request: chat description is not modified")] ChatDescriptionIsNotModified, @@ -292,7 +292,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`AnswerCallbackQuery`] /// - /// [`AnswerCallbackQuery`]: crate::requests::AnswerCallbackQuery + /// [`AnswerCallbackQuery`]: crate::payloads::AnswerCallbackQuery #[serde(rename = "Bad Request: query is too old and response timeout expired or query id is \ invalid")] InvalidQueryID, @@ -303,7 +303,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::SendMessage + /// [`SendMessage`]: crate::payloads::SendMessage #[serde(rename = "Bad Request: BUTTON_URL_INVALID")] ButtonURLInvalid, @@ -312,7 +312,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::SendMessage + /// [`SendMessage`]: crate::payloads::SendMessage #[serde(rename = "Bad Request: BUTTON_DATA_INVALID")] ButtonDataInvalid, @@ -321,7 +321,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::SendMessage + /// [`SendMessage`]: crate::payloads::SendMessage #[serde(rename = "Bad Request: can't parse inline keyboard button: Text buttons are \ unallowed in the inline keyboard")] TextButtonsAreUnallowed, @@ -331,7 +331,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`GetFile`] /// - /// [`GetFile`]: crate::requests::GetFile + /// [`GetFile`]: crate::payloads::GetFile #[serde(rename = "Bad Request: wrong file id")] WrongFileID, @@ -344,7 +344,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SetChatPhoto`] /// - /// [`SetChatPhoto`]: crate::requests::SetChatPhoto + /// [`SetChatPhoto`]: crate::payloads::SetChatPhoto #[serde(rename = "Bad Request: Photo should be uploaded as an InputFile")] PhotoAsInputFileRequired, @@ -353,7 +353,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`AddStickerToSet`] /// - /// [`AddStickerToSet`]: crate::requests::AddStickerToSet + /// [`AddStickerToSet`]: crate::payloads::AddStickerToSet #[serde(rename = "Bad Request: STICKERSET_INVALID")] InvalidStickersSet, @@ -363,7 +363,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`PinChatMessage`] /// - /// [`PinChatMessage`]: crate::requests::PinChatMessage + /// [`PinChatMessage`]: crate::payloads::PinChatMessage #[serde(rename = "Bad Request: not enough rights to pin a message")] NotEnoughRightsToPinMessage, @@ -377,7 +377,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`PromoteChatMember`] /// - /// [`PromoteChatMember`]: crate::requests::PromoteChatMember + /// [`PromoteChatMember`]: crate::payloads::PromoteChatMember #[serde(rename = "Bad Request: can't demote chat creator")] CantDemoteChatCreator, @@ -386,7 +386,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`RestrictChatMember`] /// - /// [`RestrictChatMember`]: crate::requests::RestrictChatMember + /// [`RestrictChatMember`]: crate::payloads::RestrictChatMember #[serde(rename = "Bad Request: can't restrict self")] CantRestrictSelf, @@ -396,7 +396,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`RestrictChatMember`] /// - /// [`RestrictChatMember`]: crate::requests::RestrictChatMember + /// [`RestrictChatMember`]: crate::payloads::RestrictChatMember #[serde(rename = "Bad Request: not enough rights to restrict/unrestrict chat member")] NotEnoughRightsToRestrict, @@ -405,7 +405,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SetWebhook`] /// - /// [`SetWebhook`]: crate::requests::SetWebhook + /// [`SetWebhook`]: crate::payloads::SetWebhook #[serde(rename = "Bad Request: bad webhook: HTTPS url must be provided for webhook")] WebhookRequireHTTPS, @@ -415,7 +415,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SetWebhook`] /// - /// [`SetWebhook`]: crate::requests::SetWebhook + /// [`SetWebhook`]: crate::payloads::SetWebhook #[serde(rename = "Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 \ or 8443")] BadWebhookPort, @@ -425,7 +425,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SetWebhook`] /// - /// [`SetWebhook`]: crate::requests::SetWebhook + /// [`SetWebhook`]: crate::payloads::SetWebhook #[serde(rename = "Bad Request: bad webhook: Failed to resolve host: Name or service not known")] UnknownHost, @@ -434,7 +434,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SetWebhook`] /// - /// [`SetWebhook`]: crate::requests::SetWebhook + /// [`SetWebhook`]: crate::payloads::SetWebhook #[serde(rename = "Bad Request: can't parse URL")] CantParseUrl, @@ -443,7 +443,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::SendMessage + /// [`SendMessage`]: crate::payloads::SendMessage #[serde(rename = "Bad Request: can't parse entities")] CantParseEntities, @@ -452,7 +452,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`GetUpdates`] /// - /// [`GetUpdates`]: crate::requests::GetUpdates + /// [`GetUpdates`]: crate::payloads::GetUpdates #[serde(rename = "can't use getUpdates method while webhook is active")] CantGetUpdates, @@ -461,7 +461,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::SendMessage + /// [`SendMessage`]: crate::payloads::SendMessage #[serde(rename = "Unauthorized: bot was kicked from a chat")] BotKicked, @@ -470,7 +470,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::SendMessage + /// [`SendMessage`]: crate::payloads::SendMessage #[serde(rename = "Unauthorized: user is deactivated")] UserDeactivated, @@ -479,7 +479,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::SendMessage + /// [`SendMessage`]: crate::payloads::SendMessage #[serde(rename = "Unauthorized: bot can't initiate conversation with a user")] CantInitiateConversation, @@ -488,7 +488,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::SendMessage + /// [`SendMessage`]: crate::payloads::SendMessage #[serde(rename = "Unauthorized: bot can't send messages to bots")] CantTalkWithBots, @@ -497,7 +497,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::SendMessage + /// [`SendMessage`]: crate::payloads::SendMessage #[serde(rename = "Bad Request: wrong HTTP URL")] WrongHTTPurl, @@ -507,7 +507,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`GetUpdates`] /// - /// [`GetUpdates`]: crate::requests::GetUpdates + /// [`GetUpdates`]: crate::payloads::GetUpdates #[serde(rename = "Conflict: terminated by other getUpdates request; make sure that only one \ bot instance is running")] TerminatedByOtherGetUpdates, @@ -517,7 +517,7 @@ pub enum ApiError { /// May happen in methods: /// 1. [`GetFile`] /// - /// [`GetFile`]: crate::requests::GetFile + /// [`GetFile`]: crate::payloads::GetFile #[serde(rename = "Bad Request: invalid file id")] FileIdInvalid, diff --git a/src/types/chat.rs b/src/types/chat.rs index 2623614a..3a9e7dfe 100644 --- a/src/types/chat.rs +++ b/src/types/chat.rs @@ -18,9 +18,9 @@ pub struct Chat { #[serde(flatten)] pub kind: ChatKind, - /// A chat photo. Returned only in [`Bot::get_chat`]. + /// A chat photo. Returned only in [`GetChat`]. /// - /// [`Bot::get_chat`]: crate::Bot::get_chat + /// [`GetChat`]: crate::payloads::GetChat pub photo: Option, } @@ -63,27 +63,27 @@ pub struct ChatPublic { pub kind: PublicChatKind, /// A description, for groups, supergroups and channel chats. Returned - /// only in [`Bot::get_chat`]. + /// only in [`GetChat`]. /// - /// [`Bot::get_chat`]: crate::Bot::get_chat + /// [`GetChat`]: crate::payloads::GetChat pub description: Option, /// A chat invite link, for groups, supergroups and channel chats. Each /// administrator in a chat generates their own invite links, so the /// bot must first generate the link using - /// [`Bot::export_chat_invite_link`]. Returned only in - /// [`Bot::get_chat`]. + /// [`ExportChatInviteLink`]. Returned only in + /// [`GetChat`]. /// - /// [`Bot::export_chat_invite_link`]: - /// crate::Bot::export_chat_invite_link + /// [`ExportChatInviteLink`]: + /// crate::payloads::ExportChatInviteLink /// - /// [`Bot::get_chat`]: crate::Bot::get_chat + /// [`GetChat`]: crate::payloads::GetChat pub invite_link: Option, /// Pinned message, for groups, supergroups and channels. Returned only - /// in [`Bot::get_chat`]. + /// in [`GetChat`]. /// - /// [`Bot::get_chat`]: crate::Bot::get_chat + /// [`GetChat`]: crate::payloads::GetChat pub pinned_message: Option>, } @@ -199,9 +199,9 @@ impl PublicChatChannel { #[derive(Clone, Default, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct PublicChatGroup { /// A default chat member permissions, for groups and supergroups. Returned - /// only from [`Bot::get_chat`]. + /// only from [`GetChat`]. /// - /// [`Bot::get_chat`]: crate::Bot::get_chat + /// [`GetChat`]: crate::payloads::GetChat pub permissions: Option, } @@ -219,27 +219,27 @@ pub struct PublicChatSupergroup { pub username: Option, /// For supergroups, name of group sticker set. Returned only from - /// [`Bot::get_chat`]. + /// [`GetChat`]. /// - /// [`Bot::get_chat`]: crate::Bot::get_chat + /// [`GetChat`]: crate::payloads::GetChat pub sticker_set_name: Option, /// `true`, if the bot can change the group sticker set. Returned only - /// from [`Bot::get_chat`]. + /// from [`GetChat`]. /// - /// [`Bot::get_chat`]: crate::Bot::get_chat + /// [`GetChat`]: crate::payloads::GetChat pub can_set_sticker_set: Option, /// A default chat member permissions, for groups and supergroups. - /// Returned only from [`Bot::get_chat`]. + /// Returned only from [`GetChat`]. /// - /// [`Bot::get_chat`]: crate::Bot::get_chat + /// [`GetChat`]: crate::payloads::GetChat pub permissions: Option, /// The minimum allowed delay between consecutive messages sent by each - /// unpriviledged user. Returned only from [`Bot::get_chat`]. + /// unpriviledged user. Returned only from [`GetChat`]. /// - /// [`Bot::get_chat`]: crate::Bot::get_chat + /// [`GetChat`]: crate::payloads::GetChat pub slow_mode_delay: Option, } diff --git a/src/types/file.rs b/src/types/file.rs index 43734b6d..86908109 100644 --- a/src/types/file.rs +++ b/src/types/file.rs @@ -4,11 +4,11 @@ use serde::{Deserialize, Serialize}; /// /// 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`]. +/// link expires, a new one can be requested by calling [`GetFile`]. /// /// [The official docs](https://core.telegram.org/bots/api#file). /// -/// [`Bot::get_file`]: crate::Bot::get_file +/// [`GetFile`]: crate::payloads::GetFile #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct File { /// Identifier for this file. diff --git a/src/types/game.rs b/src/types/game.rs index 56f4ef69..477f7988 100644 --- a/src/types/game.rs +++ b/src/types/game.rs @@ -20,14 +20,15 @@ pub struct Game { /// Photo that will be displayed in the game message in chats. pub photo: Vec, + // FIXME(waffle): SetGameScore method is missing for some reason O_o + #[allow(broken_intra_doc_links)] /// Brief description of the game or high scores included in the game /// message. Can be automatically edited to include current high scores - /// for the game when the bot calls [`Bot::set_game_score`], or manually - /// edited using [`Bot::edit_message_text`]. 0-4096 characters. + /// for the game when the bot calls [`SetGameScore`], or manually + /// edited using [`EditMessageText`]. 0-4096 characters. /// - /// [`Bot::set_game_score`]: crate::Bot::set_game_score - /// - /// [`Bot::edit_message_text`]: crate::Bot::edit_message_text + /// [`SetGameScore`]: crate::payloads::SetGameScore + /// [`EditMessageText`]: crate::payloads::EditMessageText pub text: Option, /// Special entities that appear in text, such as usernames, URLs, bot diff --git a/src/types/me.rs b/src/types/me.rs index a9f33848..bf07b50f 100644 --- a/src/types/me.rs +++ b/src/types/me.rs @@ -1,9 +1,9 @@ use crate::types::User; use serde::{Deserialize, Serialize}; -/// Returned only in [`Bot::get_me`]. +/// Returned only in [`GetMe`]. /// -/// [`Bot::get_me`]: crate::Bot::get_me +/// [`GetMe`]: crate::payloads::GetMe #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct Me { #[serde(flatten)] diff --git a/src/types/update.rs b/src/types/update.rs index e77656c1..8aa3992f 100644 --- a/src/types/update.rs +++ b/src/types/update.rs @@ -16,13 +16,11 @@ use crate::types::{ pub struct Update { /// The update‘s unique identifier. Update identifiers start from a certain /// positive number and increase sequentially. This ID becomes especially - /// handy if you’re using [Webhooks], since it allows you to ignore + /// handy if you’re using webhooks, since it allows you to ignore /// repeated updates or to restore the correct update sequence, should /// they get out of order. If there are no new updates for at least a /// week, then identifier of the next update will be chosen randomly /// instead of sequentially. - /// - /// [Webhooks]: crate::Bot::set_webhook #[serde(rename = "update_id")] pub id: i32, From 445c42b585e386d314c81047c1db8a49a72e54b1 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 12 Dec 2020 15:52:47 +0300 Subject: [PATCH 161/755] Refactor file downloading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Make `net` module public - Move `Bot::download_file{,_stream}` methods to a new `Download` trait - Add `download_forward` macro to forward `Download` impls (priv) - Impl `Download` for all bot adaptors & the `Bot` itself - Change return type of `download_file_stream` — return `Stream>``, instead of `Future>>>`` - Add `api_url` param to standalone versions of `download_file{,_stream}` - Make `net::{TELEGRAM_API_URL, download_file{,_stream}}` pub - Small documentation changes --- src/adaptors/auto_send.rs | 7 ++ src/adaptors/cache_me.rs | 7 ++ src/adaptors/parse_mode.rs | 7 ++ src/adaptors/throttle.rs | 7 ++ src/bot/download.rs | 73 +++++------------ src/lib.rs | 2 +- src/local_macros.rs | 31 ++++++++ src/net/download.rs | 157 +++++++++++++++++++++++++++---------- src/net/mod.rs | 12 ++- src/requests/multipart.rs | 2 +- src/types/file.rs | 15 ++-- 11 files changed, 217 insertions(+), 103 deletions(-) diff --git a/src/adaptors/auto_send.rs b/src/adaptors/auto_send.rs index f5c71e61..54a85d87 100644 --- a/src/adaptors/auto_send.rs +++ b/src/adaptors/auto_send.rs @@ -96,6 +96,13 @@ impl Requester for AutoSend { } } +download_forward! { + 'w + B + AutoSend + { this => this.inner() } +} + #[pin_project::pin_project] pub struct AutoRequest(#[pin] Inner); diff --git a/src/adaptors/cache_me.rs b/src/adaptors/cache_me.rs index 3f8000fe..8f54a2ee 100644 --- a/src/adaptors/cache_me.rs +++ b/src/adaptors/cache_me.rs @@ -113,6 +113,13 @@ where } } +download_forward! { + 'w + B + CacheMe + { this => this.inner() } +} + pub struct CachedMeRequest>(Inner, GetMe); enum Inner> { diff --git a/src/adaptors/parse_mode.rs b/src/adaptors/parse_mode.rs index e191e73d..ce4814d9 100644 --- a/src/adaptors/parse_mode.rs +++ b/src/adaptors/parse_mode.rs @@ -106,3 +106,10 @@ impl Requester for DefaultParseMode { set_sticker_set_thumb, send_invoice, answer_shipping_query, answer_pre_checkout_query, set_passport_data_errors => fid, fty } } + +download_forward! { + 'w + B + DefaultParseMode + { this => this.inner() } +} diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index 2401e04c..e7e2250c 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -457,6 +457,13 @@ where } } +download_forward! { + 'w + B + Throttle + { this => this.inner() } +} + /// Id used in worker. /// /// It is used instead of `ChatId` to make copying cheap even in case of diff --git a/src/bot/download.rs b/src/bot/download.rs index bc859df7..dad76b31 100644 --- a/src/bot/download.rs +++ b/src/bot/download.rs @@ -1,66 +1,33 @@ use bytes::Bytes; -use tokio::{io::AsyncWrite, stream::Stream}; +use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt}; +use tokio::io::AsyncWrite; use crate::{ bot::Bot, - net::{download_file, download_file_stream}, + net::{self, Download}, DownloadError, }; -impl Bot { - /// Download a file from Telegram into `destination`. - /// - /// `path` can be obtained from [`GetFile`]. - /// - /// To download as a stream of chunks, see [`Bot::download_file_stream`]. - /// - /// ## Examples - /// - /// ```no_run - /// use teloxide_core::{ - /// requests::{Request, Requester}, - /// types::File as TgFile, - /// Bot, - /// }; - /// use tokio::fs::File; - /// - /// # async fn run() -> Result<(), Box> { - /// let bot = Bot::new("TOKEN"); - /// - /// let TgFile { file_path, .. } = bot.get_file("*file_id*").send().await?; - /// let mut file = File::create("/home/waffle/Pictures/test.png").await?; - /// bot.download_file(&file_path, &mut file).await?; - /// # Ok(()) } - /// ``` - /// - /// [`GetFile`]: crate::payloads::GetFile - /// [`Bot::download_file_stream`]: crate::Bot::download_file_stream - pub async fn download_file( +impl<'w> Download<'w> for Bot { + type Err = DownloadError; + + // I would like to unbox this, but my coworkers will kill me if they'll see yet + // another hand written `Future`. (waffle) + type Fut = BoxFuture<'w, Result<(), Self::Err>>; + + fn download_file( &self, path: &str, - destination: &mut D, - ) -> Result<(), DownloadError> - where - D: AsyncWrite + Unpin, - { - download_file(&self.client, &self.token, path, destination).await + destination: &'w mut (dyn AsyncWrite + Unpin + Send), + ) -> Self::Fut { + net::download_file(&self.client, self.api_url.get(), &self.token, path, destination).boxed() } - /// Download a file from Telegram. - /// - /// `path` can be obtained from the [`GetFile`]. - /// - /// To download into [`AsyncWrite`] (e.g. [`tokio::fs::File`]), see - /// [`Bot::download_file`]. - /// - /// [`GetFile`]: crate::payloads::GetFile - /// [`AsyncWrite`]: tokio::io::AsyncWrite - /// [`tokio::fs::File`]: tokio::fs::File - /// [`Bot::download_file`]: crate::Bot::download_file - pub async fn download_file_stream( - &self, - path: &str, - ) -> Result>, reqwest::Error> { - download_file_stream(&self.client, &self.token, path).await + type StreamErr = reqwest::Error; + + type Stream = BoxStream<'static, Result>; + + fn download_file_stream(&self, path: &str) -> Self::Stream { + net::download_file_stream(&self.client, self.api_url.get(), &self.token, path).boxed() } } diff --git a/src/lib.rs b/src/lib.rs index 54d79c30..7b33b931 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ pub use self::{ }; pub mod adaptors; +pub mod net; pub mod payloads; pub mod prelude; pub mod requests; @@ -34,7 +35,6 @@ mod bot; mod errors; // implementation details -mod net; mod serde_multipart; /// Constructs a client from the `TELOXIDE_PROXY` environmental variable. diff --git a/src/local_macros.rs b/src/local_macros.rs index 83bf0f57..51be0d62 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -995,3 +995,34 @@ macro_rules! requester_forward { } }; } + +#[macro_use] +macro_rules! download_forward { + ($l:lifetime $T:ident $S:ty {$this:ident => $inner:expr}) => { + impl<$l, $T: crate::net::Download<$l>> crate::net::Download<$l> for $S { + type Err = <$T as crate::net::Download<$l>>::Err; + + type Fut = <$T as crate::net::Download<$l>>::Fut; + + fn download_file( + &self, + path: &str, + destination: &'w mut (dyn tokio::io::AsyncWrite + + core::marker::Unpin + + core::marker::Send), + ) -> Self::Fut { + let $this = self; + ($inner).download_file(path, destination) + } + + type StreamErr = <$T as crate::net::Download<$l>>::StreamErr; + + type Stream = <$T as crate::net::Download<$l>>::Stream; + + fn download_file_stream(&self, path: &str) -> Self::Stream { + let $this = self; + ($inner).download_file_stream(path) + } + } + }; +} diff --git a/src/net/download.rs b/src/net/download.rs index 3a507ad2..d19de588 100644 --- a/src/net/download.rs +++ b/src/net/download.rs @@ -1,54 +1,131 @@ -use reqwest::Client; +use std::future::Future; + +use bytes::Bytes; +use futures::{ + future::{ready, Either}, + stream::{once, unfold}, + FutureExt, Stream, StreamExt, +}; +use reqwest::{Client, Response, Url}; use tokio::io::{AsyncWrite, AsyncWriteExt}; -use crate::errors::DownloadError; +use crate::{errors::DownloadError, net::file_url}; -pub async fn download_file( - client: &Client, - token: &str, - path: &str, - destination: &mut D, -) -> Result<(), DownloadError> -where - D: AsyncWrite + Unpin, +/// Download client. +/// +/// This trait allows you to download files from telegram. +pub trait Download<'w> +/* FIXME(waffle): ideally, this lifetime ('w) shouldn't be here, but we can't help it without + * GATs */ { - let mut res = client - .get(crate::net::file_url( - reqwest::Url::parse(crate::net::TELEGRAM_API_URL).expect("failed to parse default url"), - token, - path, - )) - .send() - .await? - .error_for_status()?; + /// Error returned by [`download_file`](Self::download_file) + type Err; - while let Some(chunk) = res.chunk().await? { - destination.write_all(&chunk).await?; - } + /// Future returned from [`download_file`](Self::download_file) + type Fut: Future> + Send; - Ok(()) + /// Download a file from Telegram into `destination`. + /// + /// `path` can be obtained from [`GetFile`]. + /// + /// To download as a stream of chunks, see [`download_file_stream`]. + /// + /// ## Examples + /// + /// ```no_run + /// use teloxide_core::{ + /// requests::{Download, Request, Requester}, + /// types::File as TgFile, + /// Bot, + /// }; + /// use tokio::fs::File; + /// + /// # async fn run() -> Result<(), Box> { + /// let bot = Bot::new("TOKEN"); + /// + /// let TgFile { file_path, .. } = bot.get_file("*file_id*").send().await?; + /// let mut file = File::create("/tmp/test.png").await?; + /// bot.download_file(&file_path, &mut file).await?; + /// # Ok(()) } + /// ``` + /// + /// [`GetFile`]: crate::payloads::GetFile + /// [`download_file_stream`]: Self::download_file_stream + fn download_file( + &self, + path: &str, + destination: &'w mut (dyn AsyncWrite + Unpin + Send), + ) -> Self::Fut; + + /// Error returned by [`download_file_stream`](Self::download_file_stream) + type StreamErr; + + /// Stream returned from [`download_file_stream`] + /// + ///[`download_file_stream`]: (Self::download_file_stream) + type Stream: Stream> + Send; + + /// Download a file from Telegram as a [`Stream`]. + /// + /// `path` can be obtained from the [`GetFile`]. + /// + /// To download into an [`AsyncWrite`] (e.g. [`tokio::fs::File`]), see + /// [`download_file`]. + /// + /// [`GetFile`]: crate::payloads::GetFile + /// [`AsyncWrite`]: tokio::io::AsyncWrite + /// [`tokio::fs::File`]: tokio::fs::File + /// [`download_file`]: Self::download_file + fn download_file_stream(&self, path: &str) -> Self::Stream; } -pub async fn download_file_stream( +/// Download a file from Telegram into `dst`. +/// +/// Note: if you don't need to use a different (from you're bot) client and +/// don't need to get *all* performance (and you don't, c'mon it's very io-bound +/// job), then it's recommended to use [`Download::download_file`] +pub fn download_file<'o, D>( client: &Client, + api_url: Url, token: &str, path: &str, -) -> Result>, reqwest::Error> { - let res = client - .get(crate::net::file_url( - reqwest::Url::parse(crate::net::TELEGRAM_API_URL).expect("failed to parse default url"), - token, - path, - )) - .send() - .await? - .error_for_status()?; + dst: &'o mut D, +) -> impl Future> + 'o +where + D: ?Sized + AsyncWrite + Unpin, +{ + client.get(file_url(api_url, token, path)).send().then(move |r| async move { + let mut res = r?.error_for_status()?; - Ok(futures::stream::unfold(res, |mut res| async { - match res.chunk().await { - Err(err) => Some((Err(err), res)), - Ok(Some(c)) => Some((Ok(c), res)), - Ok(None) => None, + while let Some(chunk) = res.chunk().await? { + dst.write_all(&chunk).await?; } - })) + + Ok(()) + }) +} + +/// Download a file from Telegram as a [`Stream`]. +/// +/// Note: if you don't need to use a different (from you're bot) client and +/// don't need to get *all* performance (and you don't, c'mon it's very io-bound +/// job), then it's recommended to use [`Download::download_file_stream`] +pub fn download_file_stream( + client: &Client, + api_url: Url, + token: &str, + path: &str, +) -> impl Stream> + 'static { + client.get(file_url(api_url, token, path)).send().into_stream().flat_map(|res| { + match res.and_then(Response::error_for_status) { + Ok(res) => Either::Left(unfold(res, |mut res| async { + match res.chunk().await { + Err(err) => Some((Err(err), res)), + Ok(Some(c)) => Some((Ok(c), res)), + Ok(None) => None, + } + })), + Err(err) => Either::Right(once(ready(Err(err)))), + } + }) } diff --git a/src/net/mod.rs b/src/net/mod.rs index a6699ca5..cac0f366 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -1,5 +1,12 @@ +//! Network specific items. +//! +//! Currently this module contains only the file download stuff and the default +//! api url. +// ... and some internal stuff :P + +pub use self::download::{download_file, download_file_stream, Download}; + pub(crate) use self::{ - download::{download_file, download_file_stream}, request::{request_json, request_multipart}, telegram_response::TelegramResponse, }; @@ -8,7 +15,8 @@ mod download; mod request; mod telegram_response; -pub(crate) const TELEGRAM_API_URL: &str = "https://api.telegram.org"; +/// Default telegram api url +pub const TELEGRAM_API_URL: &str = "https://api.telegram.org"; /// Creates URL for making HTTPS requests. See the [Telegram documentation]. /// diff --git a/src/requests/multipart.rs b/src/requests/multipart.rs index 009271bd..41339055 100644 --- a/src/requests/multipart.rs +++ b/src/requests/multipart.rs @@ -18,7 +18,7 @@ pub struct MultipartRequest

{ } impl

MultipartRequest

{ - pub fn new(bot: Bot, payload: P) -> Self { + pub const fn new(bot: Bot, payload: P) -> Self { Self { bot, payload } } } diff --git a/src/types/file.rs b/src/types/file.rs index 86908109..a1eeeb75 100644 --- a/src/types/file.rs +++ b/src/types/file.rs @@ -2,13 +2,15 @@ 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/`. -/// 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 [`GetFile`]. +/// The file can be downloaded via the [`Bot::download_file(file_path, dst)`] +/// method. It is guaranteed that the path from [`GetFile`] will be valid for at +/// least 1 hour. When the path expires, a new one can be requested by calling +/// [`GetFile`]. /// /// [The official docs](https://core.telegram.org/bots/api#file). /// /// [`GetFile`]: crate::payloads::GetFile +/// [`Bot::download_file(file_path, dst)`]: crate::net::Download::download_file #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct File { /// Identifier for this file. @@ -22,9 +24,10 @@ pub struct File { /// File size, if known. pub file_size: u32, - // TODO: chacge "Use ..." to use bot.download... - /// File path. Use `https://api.telegram.org/file/bot/` - /// to get the file. + /// File path. Use [`Bot::download_file(file_path, dst)`] to get the file. + /// + /// [`Bot::download_file(file_path, dst)`]: + /// crate::net::Download::download_file pub file_path: String, } From 290258cd1fa64ea6123fbf128961dccd87d0d177 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 12 Dec 2020 17:59:45 +0300 Subject: [PATCH 162/755] Small fixes: fmt & import in test --- src/net/download.rs | 3 ++- src/net/telegram_response.rs | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/net/download.rs b/src/net/download.rs index d19de588..16694821 100644 --- a/src/net/download.rs +++ b/src/net/download.rs @@ -34,7 +34,8 @@ pub trait Download<'w> /// /// ```no_run /// use teloxide_core::{ - /// requests::{Download, Request, Requester}, + /// net::Download, + /// requests::{Request, Requester}, /// types::File as TgFile, /// Bot, /// }; diff --git a/src/net/telegram_response.rs b/src/net/telegram_response.rs index fd3dbc9e..04f8f4c1 100644 --- a/src/net/telegram_response.rs +++ b/src/net/telegram_response.rs @@ -62,9 +62,10 @@ mod tests { let s = r#"{"ok":false,"error_code":409,"description":"Conflict: terminated by other getUpdates request; make sure that only one bot instance is running"}"#; let val = serde_json::from_str::>(s).unwrap(); - assert!( - matches!(val, TelegramResponse::Err { error: ApiError::TerminatedByOtherGetUpdates, .. }) - ); + assert!(matches!( + val, + TelegramResponse::Err { error: ApiError::TerminatedByOtherGetUpdates, .. } + )); } #[test] From 08cefc379b2f0136183c98274879dccd18ad76ea Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 12 Dec 2020 19:27:54 +0300 Subject: [PATCH 163/755] Fix netlify docs build --- netlify.toml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/netlify.toml b/netlify.toml index ecb9c8fb..be511e64 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,12 +1,8 @@ -[build] -# Directory (relative to root of your repo) that contains the deploy-ready -# HTML files and assets generated by the build. If a base directory has -# been specified, include it in the publish directory path. -publish = "target/doc" - -# Default build command. -command = 'curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly --profile minimal && source $HOME/.cargo/env && RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --no-deps --all-features' +[build] +command = "rustup install nightly --profile minimal && cargo +nightly doc --all-features --no-deps && cp -r target/doc _netlify_out" +environment = { RUSTDOCFLAGS= "--cfg docsrs" } +publish = "_netlify_out" [[redirects]] - from = "/*" - to = "/teloxide_core" +from = "/" +to = "/teloxide_core" From f5d9cb1a73c0a66120f7b444549e831f7f320fde Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 23 Dec 2020 22:24:31 +0600 Subject: [PATCH 164/755] Documentation gardening --- src/adaptors.rs | 11 ++- src/adaptors/parse_mode.rs | 8 +- src/errors.rs | 15 ++-- src/net/download.rs | 21 +++-- src/net/mod.rs | 8 +- src/payloads/mod.rs | 9 +- src/prelude.rs | 2 +- src/requests/has_payload.rs | 15 ++-- src/requests/json.rs | 6 +- src/requests/mod.rs | 3 +- src/requests/multipart.rs | 7 +- src/requests/payload.rs | 24 +++--- src/requests/request.rs | 23 +++--- src/requests/requester.rs | 151 +++++++++++++++++----------------- src/requests/requester_ext.rs | 1 + 15 files changed, 152 insertions(+), 152 deletions(-) diff --git a/src/adaptors.rs b/src/adaptors.rs index 40982784..fc4c6583 100644 --- a/src/adaptors.rs +++ b/src/adaptors.rs @@ -1,11 +1,10 @@ -//! ## Bot adaptors +//! Wrappers altering functionality of a bot. //! -//! Bot adaptors are very similar to [`Iterator`] adaptors — -//! they are bots (implement [`Requester`]) which wrap other bots -//! adding new functionality. +//! Bot adaptors are very similar to the [`Iterator`] adaptors: they are bots +//! wrapping other bots to alter existing or add new functionality. //! -//! E.g. [`AutoSend`] allows `await`ing requests directly, -//! without need to use `.send()`. +//! E.g. [`AutoSend`] allows `await`ing requests directly, no need to to use +//! `.send()`. //! //! [`Requester`]: crate::requests::Requester diff --git a/src/adaptors/parse_mode.rs b/src/adaptors/parse_mode.rs index ce4814d9..5057a5f1 100644 --- a/src/adaptors/parse_mode.rs +++ b/src/adaptors/parse_mode.rs @@ -5,14 +5,14 @@ use crate::{ }; /// Default parse mode adaptor, see -/// [`RequesterExt::parse_mode`](crate::requests::RequesterExt::parse_mode) +/// [`RequesterExt::parse_mode`](crate::requests::RequesterExt::parse_mode). pub struct DefaultParseMode { bot: B, mode: ParseMode, } impl DefaultParseMode { - /// Creates new [`DefaultParseMode`] + /// Creates new [`DefaultParseMode`]. /// /// Note: it's recommended to use [`RequesterExt::parse_mode`] instead. /// @@ -21,12 +21,12 @@ impl DefaultParseMode { Self { bot, mode: parse_mode } } - /// Allows to access inner bot + /// Allows to access the inner bot. pub fn inner(&self) -> &B { &self.bot } - /// Unwraps inner bot + /// Unwraps the inner bot. pub fn into_inner(self) -> B { self.bot } diff --git a/src/errors.rs b/src/errors.rs index 1164398a..1b183180 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -8,11 +8,11 @@ use thiserror::Error; /// An error caused by downloading a file. #[derive(Debug, Error, From)] pub enum DownloadError { - /// Network error while downloading file from telegram. + /// Network error while downloading a file from Telegram. #[error("A network error: {0}")] NetworkError(#[source] reqwest::Error), - /// I/O error while writing file to destination. + /// An I/O error while writing a file to destination. #[error("An I/O error: {0}")] Io(#[source] std::io::Error), } @@ -20,12 +20,13 @@ pub enum DownloadError { /// An error caused by sending a request to Telegram. #[derive(Debug, Error)] pub enum RequestError { - /// Telegram API error + /// Telegram API error. #[error("A Telegram's error #{status_code}: {kind:?}")] ApiError { - /// Kind of api error + /// Kind of an API error. kind: ApiError, - /// HTTP code returned by telegram, not very usefull in practice. + + /// An HTTP code returned by Telegram, not very useful in practice. status_code: StatusCode, }, @@ -39,11 +40,11 @@ pub enum RequestError { #[error("Retry after {0} seconds")] RetryAfter(i32), - /// Network error while sending request to telegram. + /// Network error while sending a request to Telegram. #[error("A network error: {0}")] NetworkError(#[source] reqwest::Error), - /// Error while parsing response from telegram. + /// Error while parsing a response from Telegram. /// /// If you've received this error, please, [open an issue] with the /// description of the error. diff --git a/src/net/download.rs b/src/net/download.rs index 16694821..5c123cb7 100644 --- a/src/net/download.rs +++ b/src/net/download.rs @@ -11,17 +11,15 @@ use tokio::io::{AsyncWrite, AsyncWriteExt}; use crate::{errors::DownloadError, net::file_url}; -/// Download client. -/// -/// This trait allows you to download files from telegram. +/// A trait for downloading files from Telegram. pub trait Download<'w> /* FIXME(waffle): ideally, this lifetime ('w) shouldn't be here, but we can't help it without * GATs */ { - /// Error returned by [`download_file`](Self::download_file) + /// An error returned from [`download_file`](Self::download_file). type Err; - /// Future returned from [`download_file`](Self::download_file) + /// A future returned from [`download_file`](Self::download_file). type Fut: Future> + Send; /// Download a file from Telegram into `destination`. @@ -58,15 +56,16 @@ pub trait Download<'w> destination: &'w mut (dyn AsyncWrite + Unpin + Send), ) -> Self::Fut; - /// Error returned by [`download_file_stream`](Self::download_file_stream) + /// An error returned from + /// [`download_file_stream`](Self::download_file_stream). type StreamErr; - /// Stream returned from [`download_file_stream`] + /// A stream returned from [`download_file_stream`]. /// ///[`download_file_stream`]: (Self::download_file_stream) type Stream: Stream> + Send; - /// Download a file from Telegram as a [`Stream`]. + /// Download a file from Telegram as [`Stream`]. /// /// `path` can be obtained from the [`GetFile`]. /// @@ -84,7 +83,7 @@ pub trait Download<'w> /// /// Note: if you don't need to use a different (from you're bot) client and /// don't need to get *all* performance (and you don't, c'mon it's very io-bound -/// job), then it's recommended to use [`Download::download_file`] +/// job), then it's recommended to use [`Download::download_file`]. pub fn download_file<'o, D>( client: &Client, api_url: Url, @@ -106,11 +105,11 @@ where }) } -/// Download a file from Telegram as a [`Stream`]. +/// Download a file from Telegram as [`Stream`]. /// /// Note: if you don't need to use a different (from you're bot) client and /// don't need to get *all* performance (and you don't, c'mon it's very io-bound -/// job), then it's recommended to use [`Download::download_file_stream`] +/// job), then it's recommended to use [`Download::download_file_stream`]. pub fn download_file_stream( client: &Client, api_url: Url, diff --git a/src/net/mod.rs b/src/net/mod.rs index cac0f366..119213aa 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -1,8 +1,4 @@ -//! Network specific items. -//! -//! Currently this module contains only the file download stuff and the default -//! api url. -// ... and some internal stuff :P +//! Network-specific API. pub use self::download::{download_file, download_file_stream, Download}; @@ -15,7 +11,7 @@ mod download; mod request; mod telegram_response; -/// Default telegram api url +/// The default Telegram API URL. pub const TELEGRAM_API_URL: &str = "https://api.telegram.org"; /// Creates URL for making HTTPS requests. See the [Telegram documentation]. diff --git a/src/payloads/mod.rs b/src/payloads/mod.rs index 171588be..42b90fe7 100644 --- a/src/payloads/mod.rs +++ b/src/payloads/mod.rs @@ -1,11 +1,14 @@ -//! Payloads - data types sended to relegram +//! Request data sent to Telegram. -/// This module reexports all setters traits as `_`. When used with a glob +/// This module re-exports all the setters traits as `_`. When used with a glob /// import: +/// /// ``` /// use teloxide_core::payloads::setters::*; /// ``` -/// It allows you to use all payloads setters, without polluting your namespace. +/// +/// It allows you to use all the payloads setters, without polluting your +/// namespace. pub mod setters; // This block is auto generated by `cg` (878e847). diff --git a/src/prelude.rs b/src/prelude.rs index 886b201a..3ea2b60c 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,3 +1,3 @@ -//! Crate prelude +//! Commonly used items. pub use crate::requests::Requester; diff --git a/src/requests/has_payload.rs b/src/requests/has_payload.rs index facca0cc..6c0c1abf 100644 --- a/src/requests/has_payload.rs +++ b/src/requests/has_payload.rs @@ -1,15 +1,14 @@ use crate::requests::Payload; -/// Represent types that have payload inside it. E.g.: the payload itself or a -/// `Request`. +/// Represents types having payload inside. /// /// This trait is something between [`DerefMut`] and [`BorrowMut`] — it allows -/// only one implementation per type (the [output] is associated type, not a -/// generic), have implementations for all types `P` such `P: `[`Payload`], but -/// have no magic compiler support like [`DerefMut`] does nor does it require +/// only one implementation per type (the [output type] is associated, not +/// generic), has implementations for all types `P` such `P: `[`Payload`], but +/// has no magic compiler support like [`DerefMut`] does nor does it require /// any laws about `Eq`, `Ord` and `Hash` as [`BorrowMut`] does. /// -/// Also [output] type is bounded by [`Payload`] trait. +/// Also the [output type] is bounded by the [`Payload`] trait. /// /// This trait is mostly used to implement payload setters (on both payloads & /// requests), so you probably won't find yourself using it directly. @@ -17,13 +16,13 @@ use crate::requests::Payload; /// [`DerefMut`]: std::ops::DerefMut /// [`BorrowMut`]: std::borrow::BorrowMut /// [`Payload`]: crate::requests::Payload -/// [output]: HasPayload::Payload +/// [output type]: HasPayload::Payload pub trait HasPayload // FIXME(waffle): // we wanted to use As{Mut,Ref} here, but they doesn't work // because of https://github.com/rust-lang/rust/issues/77010 { - /// Type of the payload contained. + /// A type of the payload contained. type Payload: Payload; /// Gain mutable access to the underlying payload. diff --git a/src/requests/json.rs b/src/requests/json.rs index 51cf906a..bda409e5 100644 --- a/src/requests/json.rs +++ b/src/requests/json.rs @@ -6,11 +6,9 @@ use crate::{ RequestError, }; -/// Ready-to-send telegram request. +/// A ready-to-send Telegram request whose payload is sent using [JSON]. /// -/// Note: payload will be sent to telegram using [`json`] -/// -/// [`json`]: https://core.telegram.org/bots/api#making-requests +/// [JSON]: https://core.telegram.org/bots/api#making-requests #[must_use = "requests do nothing until sent"] pub struct JsonRequest

{ bot: Bot, diff --git a/src/requests/mod.rs b/src/requests/mod.rs index 3433c81e..84e933e6 100644 --- a/src/requests/mod.rs +++ b/src/requests/mod.rs @@ -8,8 +8,7 @@ pub use self::{ /// A type that is returned after making a request to Telegram. pub type ResponseResult = Result; -/// Output of a [`Payload`] in [`HasPayload`]. Alias to -/// `<::Payload as Payload>::Output`. +/// An output type of [`Payload`] in [`HasPayload`]. pub type Output = <::Payload as Payload>::Output; mod has_payload; diff --git a/src/requests/multipart.rs b/src/requests/multipart.rs index 41339055..5b2ea867 100644 --- a/src/requests/multipart.rs +++ b/src/requests/multipart.rs @@ -6,11 +6,10 @@ use crate::{ RequestError, }; -/// Ready-to-send telegram request. +/// A ready-to-send Telegram request whose payload is sent using +/// [multipart/form-data]. /// -/// Note: payload will be sent to telegram using [`multipart/form-data`] -/// -/// [`multipart/form-data`]: https://core.telegram.org/bots/api#making-requests +/// [multipart/form-data]: https://core.telegram.org/bots/api#making-requests #[must_use = "requests do nothing until sent"] pub struct MultipartRequest

{ bot: Bot, diff --git a/src/requests/payload.rs b/src/requests/payload.rs index 0e02531d..8b885e96 100644 --- a/src/requests/payload.rs +++ b/src/requests/payload.rs @@ -1,20 +1,24 @@ /// Payload of a request. /// -/// Simply speaking structs implementing this trait represent arguments of -/// a telegram bot API method. +/// Simply speaking, structures implementing this trait represent arguments of +/// a Telegram bot API method. /// -/// This trait provides some additional information needed for sending request -/// to the telegram. +/// Also, this trait provides some additional information needed for sending a +/// request to Telegram. #[cfg_attr(all(docsrs, feature = "nightly"), doc(spotlight))] pub trait Payload { - /// Return type of the telegram method. + /// A return type of a Telegram method. /// - /// Note: that should not include result wrappers (e.g. it should be simply - /// `Message`, `True` or something else) + /// Note: it should not include `Result` wrappers (e.g. it should be simply + /// [`Message`], [`True`] or something else). + /// + /// [`Message`]: crate::types::Message + /// [`True`]: crate::types::True type Output; - /// Name of the telegram method. Case insensitive, though must not include - /// underscores. (e.g.: `GetMe`, `GETME`, `getme`, `getMe` are ok, but - /// `get_me` is not ok) + /// Name of a Telegram method. + /// + /// It is case insensitive, though must not include underscores. (e.g. + /// `GetMe`, `GETME`, `getme`, `getMe` are ok, but `get_me` is not ok). const NAME: &'static str; } diff --git a/src/requests/request.rs b/src/requests/request.rs index 0d6692d9..b3f674f3 100644 --- a/src/requests/request.rs +++ b/src/requests/request.rs @@ -2,7 +2,7 @@ use std::future::Future; use crate::requests::{HasPayload, Output}; -/// A ready-to-send telegram request. +/// A ready-to-send Telegram request. // FIXME(waffle): Write better doc for the trait /// /// ## Implementation notes @@ -23,19 +23,20 @@ pub trait Request: HasPayload { * use it before it's integrated in async/await */ - /// Type of error that may happen during sending the request to telegram. + /// A type of an error that may happen while sending a request to Telegram. type Err: std::error::Error + Send; - /// Type of future returned by [`send`](Request::send) method. + /// A type of the future returned by the [`send`](Request::send) method. type Send: Future, Self::Err>> + Send; - /// Type of future returned by [`send_ref`](Request::send_ref) method. + /// A type of the future returned by the [`send_ref`](Request::send_ref) + /// method. /// - /// NOTE: it intentionally forbids borrowing from self - // though anyway we couldn't allow borrowing without GATs :sob: + /// Note: it intentionally forbids borrowing from `self` though anyway we + /// couldn't allow borrowing without GATs. type SendRef: Future, Self::Err>> + Send; - /// Send the request. + /// Send this request. /// /// ## Examples // FIXME(waffle): ignored until full request redesign lands @@ -51,15 +52,15 @@ pub trait Request: HasPayload { /// ``` fn send(self) -> Self::Send; - /// Send the request. + /// Send this request by reference. /// /// This method is analogous to [`send`](Request::send), but it doesn't take /// the ownership of `self`. This allows to send the same (or slightly /// different) requests over and over. /// - /// _Also_ it is expected that calling this method is better than just - /// `clone`ing the requests. (because instead of copying all the data - /// and then serializing it, this method should just serialize the data) + /// Also, it is expected that calling this method is better than just + /// cloning requests. (Because instead of copying all the data + /// and then serializing it, this method should just serialize the data.) /// /// ## Examples // FIXME(waffle): ignored until full request redesign lands diff --git a/src/requests/requester.rs b/src/requests/requester.rs index 5352ab35..87a78331 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -8,8 +8,9 @@ use crate::{ }, }; -/// The trait implemented by all bots & bot adaptors. -/// Essentially a request builder factory (?). +/// Methods for building requests. +/// +/// This trait is implemented by all bots & bot adaptors. /// /// _This trait is included in the crate's [`prelude`](crate::prelude)_. #[cfg_attr(all(docsrs, feature = "nightly"), doc(spotlight))] @@ -22,12 +23,12 @@ pub trait Requester { type GetUpdates: Request; - /// For telegram documentation see [`GetUpdates`] + /// For Telegram documentation see [`GetUpdates`]. fn get_updates(&self) -> Self::GetUpdates; type SetWebhook: Request; - /// For telegram documentation see [`SetWebhook`] + /// For Telegram documentation see [`SetWebhook`]. fn set_webhook(&self, url: U, allowed_updates: A) -> Self::SetWebhook where U: Into, @@ -35,22 +36,22 @@ pub trait Requester { type DeleteWebhook: Request; - /// For telegram documentation see [`DeleteWebhook`] + /// For Telegram documentation see [`DeleteWebhook`]. fn delete_webhook(&self) -> Self::DeleteWebhook; type GetWebhookInfo: Request; - /// For telegram documentation see [`GetWebhookInfo`] + /// For Telegram documentation see [`GetWebhookInfo`]. fn get_webhook_info(&self) -> Self::GetWebhookInfo; type GetMe: Request; - /// For telegram documentation see [`GetMe`] + /// For Telegram documentation see [`GetMe`]. fn get_me(&self) -> Self::GetMe; type SendMessage: Request; - /// For telegram documentation see [`SendMessage`] + /// For Telegram documentation see [`SendMessage`]. fn send_message(&self, chat_id: C, text: T) -> Self::SendMessage where C: Into, @@ -58,7 +59,7 @@ pub trait Requester { type ForwardMessage: Request; - /// For telegram documentation see [`ForwardMessage`] + /// For Telegram documentation see [`ForwardMessage`]. fn forward_message( &self, chat_id: C, @@ -71,7 +72,7 @@ pub trait Requester { type SendPhoto: Request; - /// For telegram documentation see [`SendPhoto`] + /// For Telegram documentation see [`SendPhoto`]. fn send_photo(&self, chat_id: Ch, photo: InputFile, caption: Ca) -> Self::SendPhoto where Ch: Into, @@ -79,7 +80,7 @@ pub trait Requester { type SendAudio: Request; - /// For telegram documentation see [`SendAudio`] + /// For Telegram documentation see [`SendAudio`]. fn send_audio(&self, chat_id: Ch, audio: InputFile, caption: Ca) -> Self::SendAudio where Ch: Into, @@ -87,7 +88,7 @@ pub trait Requester { type SendDocument: Request; - /// For telegram documentation see [`SendDocument`] + /// For Telegram documentation see [`SendDocument`]. fn send_document( &self, chat_id: Ch, @@ -100,7 +101,7 @@ pub trait Requester { type SendVideo: Request; - /// For telegram documentation see [`SendVideo`] + /// For Telegram documentation see [`SendVideo`]. fn send_video(&self, chat_id: Ch, video: InputFile, caption: Ca) -> Self::SendVideo where Ch: Into, @@ -108,7 +109,7 @@ pub trait Requester { type SendAnimation: Request; - /// For telegram documentation see [`SendAnimation`] + /// For Telegram documentation see [`SendAnimation`]. fn send_animation( &self, chat_id: Ch, @@ -121,7 +122,7 @@ pub trait Requester { type SendVoice: Request; - /// For telegram documentation see [`SendVoice`] + /// For Telegram documentation see [`SendVoice`]. fn send_voice(&self, chat_id: Ch, voice: InputFile, caption: Ca) -> Self::SendVoice where Ch: Into, @@ -129,14 +130,14 @@ pub trait Requester { type SendVideoNote: Request; - /// For telegram documentation see [`SendVideoNote`] + /// For Telegram documentation see [`SendVideoNote`]. fn send_video_note(&self, chat_id: C, video_note: InputFile) -> Self::SendVideoNote where C: Into; type SendMediaGroup: Request; - /// For telegram documentation see [`SendMediaGroup`] + /// For Telegram documentation see [`SendMediaGroup`]. fn send_media_group(&self, chat_id: C, media: M) -> Self::SendMediaGroup where C: Into, @@ -144,7 +145,7 @@ pub trait Requester { type SendLocation: Request; - /// For telegram documentation see [`SendLocation`] + /// For Telegram documentation see [`SendLocation`]. fn send_location( &self, chat_id: C, @@ -157,7 +158,7 @@ pub trait Requester { type EditMessageLiveLocation: Request; - /// For telegram documentation see [`EditMessageLiveLocation`] + /// For Telegram documentation see [`EditMessageLiveLocation`]. fn edit_message_live_location( &self, chat_id: C, @@ -173,7 +174,7 @@ pub trait Requester { Err = Self::Err, >; - /// For telegram documentation see [`EditMessageLiveLocationInline`] + /// For Telegram documentation see [`EditMessageLiveLocationInline`]. fn edit_message_live_location_inline( &self, inline_message_id: I, @@ -185,7 +186,7 @@ pub trait Requester { type StopMessageLiveLocation: Request; - /// For telegram documentation see [`StopMessageLiveLocation`] + /// For Telegram documentation see [`StopMessageLiveLocation`]. fn stop_message_live_location( &self, chat_id: C, @@ -201,7 +202,7 @@ pub trait Requester { Err = Self::Err, >; - /// For telegram documentation see [`StopMessageLiveLocationInline`] + /// For Telegram documentation see [`StopMessageLiveLocationInline`]. fn stop_message_live_location_inline( &self, inline_message_id: I, @@ -213,7 +214,7 @@ pub trait Requester { type SendVenue: Request; - /// For telegram documentation see [`SendVenue`] + /// For Telegram documentation see [`SendVenue`]. fn send_venue( &self, chat_id: C, @@ -229,14 +230,14 @@ pub trait Requester { type SendContact: Request; - /// For telegram documentation see [`SendContact`] + /// For Telegram documentation see [`SendContact`]. fn send_contact(&self, chat_id: C, phone_number: f64, first_name: f64) -> Self::SendContact where C: Into; type SendPoll: Request; - /// For telegram documentation see [`SendPoll`] + /// For Telegram documentation see [`SendPoll`]. fn send_poll( &self, chat_id: C, @@ -251,47 +252,47 @@ pub trait Requester { type SendDice: Request; - /// For telegram documentation see [`SendDice`] + /// For Telegram documentation see [`SendDice`]. fn send_dice(&self, chat_id: C, emoji: DiceEmoji) -> Self::SendDice where C: Into; type SendChatAction: Request; - /// For telegram documentation see [`SendChatAction`] + /// For Telegram documentation see [`SendChatAction`]. fn send_chat_action(&self, chat_id: C, action: ChatAction) -> Self::SendChatAction where C: Into; type GetUserProfilePhotos: Request; - /// For telegram documentation see [`GetUserProfilePhotos`] + /// For Telegram documentation see [`GetUserProfilePhotos`]. fn get_user_profile_photos(&self, user_id: i32) -> Self::GetUserProfilePhotos; type GetFile: Request; - /// For telegram documentation see [`GetFile`] + /// For Telegram documentation see [`GetFile`]. fn get_file(&self, file_id: F) -> Self::GetFile where F: Into; type KickChatMember: Request; - /// For telegram documentation see [`KickChatMember`] + /// For Telegram documentation see [`KickChatMember`]. fn kick_chat_member(&self, chat_id: C, user_id: i32) -> Self::KickChatMember where C: Into; type UnbanChatMember: Request; - /// For telegram documentation see [`UnbanChatMember`] + /// For Telegram documentation see [`UnbanChatMember`]. fn unban_chat_member(&self, chat_id: C, user_id: i32) -> Self::UnbanChatMember where C: Into; type RestrictChatMember: Request; - /// For telegram documentation see [`RestrictChatMember`] + /// For Telegram documentation see [`RestrictChatMember`]. fn restrict_chat_member( &self, chat_id: C, @@ -303,7 +304,7 @@ pub trait Requester { type PromoteChatMember: Request; - /// For telegram documentation see [`PromoteChatMember`] + /// For Telegram documentation see [`PromoteChatMember`]. fn promote_chat_member(&self, chat_id: C, user_id: i32) -> Self::PromoteChatMember where C: Into; @@ -313,7 +314,7 @@ pub trait Requester { Err = Self::Err, >; - /// For telegram documentation see [`SetChatAdministratorCustomTitle`] + /// For Telegram documentation see [`SetChatAdministratorCustomTitle`]. fn set_chat_administrator_custom_title( &self, chat_id: Ch, @@ -326,7 +327,7 @@ pub trait Requester { type SetChatPermissions: Request; - /// For telegram documentation see [`SetChatPermissions`] + /// For Telegram documentation see [`SetChatPermissions`]. fn set_chat_permissions( &self, chat_id: C, @@ -337,28 +338,28 @@ pub trait Requester { type ExportChatInviteLink: Request; - /// For telegram documentation see [`ExportChatInviteLink`] + /// For Telegram documentation see [`ExportChatInviteLink`]. fn export_chat_invite_link(&self, chat_id: C) -> Self::ExportChatInviteLink where C: Into; type SetChatPhoto: Request; - /// For telegram documentation see [`SetChatPhoto`] + /// For Telegram documentation see [`SetChatPhoto`]. fn set_chat_photo(&self, chat_id: C, photo: InputFile) -> Self::SetChatPhoto where C: Into; type DeleteChatPhoto: Request; - /// For telegram documentation see [`DeleteChatPhoto`] + /// For Telegram documentation see [`DeleteChatPhoto`]. fn delete_chat_photo(&self, chat_id: C) -> Self::DeleteChatPhoto where C: Into; type SetChatTitle: Request; - /// For telegram documentation see [`SetChatTitle`] + /// For Telegram documentation see [`SetChatTitle`]. fn set_chat_title(&self, chat_id: C, title: T) -> Self::SetChatTitle where C: Into, @@ -366,63 +367,63 @@ pub trait Requester { type SetChatDescription: Request; - /// For telegram documentation see [`SetChatDescription`] + /// For Telegram documentation see [`SetChatDescription`]. fn set_chat_description(&self, chat_id: C) -> Self::SetChatDescription where C: Into; type PinChatMessage: Request; - /// For telegram documentation see [`PinChatMessage`] + /// For Telegram documentation see [`PinChatMessage`]. fn pin_chat_message(&self, chat_id: C, message_id: i32) -> Self::PinChatMessage where C: Into; type UnpinChatMessage: Request; - /// For telegram documentation see [`UnpinChatMessage`] + /// For Telegram documentation see [`UnpinChatMessage`]. fn unpin_chat_message(&self, chat_id: C) -> Self::UnpinChatMessage where C: Into; type LeaveChat: Request; - /// For telegram documentation see [`LeaveChat`] + /// For Telegram documentation see [`LeaveChat`]. fn leave_chat(&self, chat_id: C) -> Self::LeaveChat where C: Into; type GetChat: Request; - /// For telegram documentation see [`GetChat`] + /// For Telegram documentation see [`GetChat`]. fn get_chat(&self, chat_id: C) -> Self::GetChat where C: Into; type GetChatAdministrators: Request; - /// For telegram documentation see [`GetChatAdministrators`] + /// For Telegram documentation see [`GetChatAdministrators`]. fn get_chat_administrators(&self, chat_id: C) -> Self::GetChatAdministrators where C: Into; type GetChatMembersCount: Request; - /// For telegram documentation see [`GetChatMembersCount`] + /// For Telegram documentation see [`GetChatMembersCount`]. fn get_chat_members_count(&self, chat_id: C) -> Self::GetChatMembersCount where C: Into; type GetChatMember: Request; - /// For telegram documentation see [`GetChatMember`] + /// For Telegram documentation see [`GetChatMember`]. fn get_chat_member(&self, chat_id: C, user_id: i32) -> Self::GetChatMember where C: Into; type SetChatStickerSet: Request; - /// For telegram documentation see [`SetChatStickerSet`] + /// For Telegram documentation see [`SetChatStickerSet`]. fn set_chat_sticker_set( &self, chat_id: C, @@ -434,33 +435,33 @@ pub trait Requester { type DeleteChatStickerSet: Request; - /// For telegram documentation see [`DeleteChatStickerSet`] + /// For Telegram documentation see [`DeleteChatStickerSet`]. fn delete_chat_sticker_set(&self, chat_id: C) -> Self::DeleteChatStickerSet where C: Into; type AnswerCallbackQuery: Request; - /// For telegram documentation see [`AnswerCallbackQuery`] + /// For Telegram documentation see [`AnswerCallbackQuery`]. fn answer_callback_query(&self, callback_query_id: C) -> Self::AnswerCallbackQuery where C: Into; type SetMyCommands: Request; - /// For telegram documentation see [`SetMyCommands`] + /// For Telegram documentation see [`SetMyCommands`]. fn set_my_commands(&self, commands: C) -> Self::SetMyCommands where C: IntoIterator; type GetMyCommands: Request; - /// For telegram documentation see [`GetMyCommands`] + /// For Telegram documentation see [`GetMyCommands`]. fn get_my_commands(&self) -> Self::GetMyCommands; type AnswerInlineQuery: Request; - /// For telegram documentation see [`AnswerInlineQuery`] + /// For Telegram documentation see [`AnswerInlineQuery`]. fn answer_inline_query(&self, inline_query_id: I, results: R) -> Self::AnswerInlineQuery where I: Into, @@ -468,7 +469,7 @@ pub trait Requester { type EditMessageText: Request; - /// For telegram documentation see [`EditMessageText`] + /// For Telegram documentation see [`EditMessageText`]. fn edit_message_text( &self, chat_id: C, @@ -481,7 +482,7 @@ pub trait Requester { type EditMessageTextInline: Request; - /// For telegram documentation see [`EditMessageTextInline`] + /// For Telegram documentation see [`EditMessageTextInline`]. fn edit_message_text_inline( &self, inline_message_id: I, @@ -493,7 +494,7 @@ pub trait Requester { type EditMessageCaption: Request; - /// For telegram documentation see [`EditMessageCaption`] + /// For Telegram documentation see [`EditMessageCaption`]. fn edit_message_caption( &self, chat_id: Ch, @@ -506,7 +507,7 @@ pub trait Requester { type EditMessageCaptionInline: Request; - /// For telegram documentation see [`EditMessageCaptionInline`] + /// For Telegram documentation see [`EditMessageCaptionInline`]. fn edit_message_caption_inline( &self, inline_message_id: I, @@ -518,7 +519,7 @@ pub trait Requester { type EditMessageMedia: Request; - /// For telegram documentation see [`EditMessageMedia`] + /// For Telegram documentation see [`EditMessageMedia`]. fn edit_message_media( &self, chat_id: C, @@ -530,7 +531,7 @@ pub trait Requester { type EditMessageMediaInline: Request; - /// For telegram documentation see [`EditMessageMediaInline`] + /// For Telegram documentation see [`EditMessageMediaInline`]. fn edit_message_media_inline( &self, inline_message_id: I, @@ -541,7 +542,7 @@ pub trait Requester { type EditMessageReplyMarkup: Request; - /// For telegram documentation see [`EditMessageReplyMarkup`] + /// For Telegram documentation see [`EditMessageReplyMarkup`]. fn edit_message_reply_markup( &self, chat_id: C, @@ -555,7 +556,7 @@ pub trait Requester { Err = Self::Err, >; - /// For telegram documentation see [`EditMessageReplyMarkupInline`] + /// For Telegram documentation see [`EditMessageReplyMarkupInline`]. fn edit_message_reply_markup_inline( &self, inline_message_id: I, @@ -565,40 +566,40 @@ pub trait Requester { type StopPoll: Request; - /// For telegram documentation see [`StopPoll`] + /// For Telegram documentation see [`StopPoll`]. fn stop_poll(&self, chat_id: C, message_id: i32) -> Self::StopPoll where C: Into; type DeleteMessage: Request; - /// For telegram documentation see [`DeleteMessage`] + /// For Telegram documentation see [`DeleteMessage`]. fn delete_message(&self, chat_id: C, message_id: i32) -> Self::DeleteMessage where C: Into; type SendSticker: Request; - /// For telegram documentation see [`SendSticker`] + /// For Telegram documentation see [`SendSticker`]. fn send_sticker(&self, chat_id: C, sticker: InputFile) -> Self::SendSticker where C: Into; type GetStickerSet: Request; - /// For telegram documentation see [`GetStickerSet`] + /// For Telegram documentation see [`GetStickerSet`]. fn get_sticker_set(&self, name: N) -> Self::GetStickerSet where N: Into; type UploadStickerFile: Request; - /// For telegram documentation see [`UploadStickerFile`] + /// For Telegram documentation see [`UploadStickerFile`]. fn upload_sticker_file(&self, user_id: i32, png_sticker: InputFile) -> Self::UploadStickerFile; type CreateNewStickerSet: Request; - /// For telegram documentation see [`CreateNewStickerSet`] + /// For Telegram documentation see [`CreateNewStickerSet`]. fn create_new_sticker_set( &self, user_id: i32, @@ -613,7 +614,7 @@ pub trait Requester { type AddStickerToSet: Request; - /// For telegram documentation see [`AddStickerToSet`] + /// For Telegram documentation see [`AddStickerToSet`]. fn add_sticker_to_set( &self, user_id: i32, @@ -627,7 +628,7 @@ pub trait Requester { type SetStickerPositionInSet: Request; - /// For telegram documentation see [`SetStickerPositionInSet`] + /// For Telegram documentation see [`SetStickerPositionInSet`]. fn set_sticker_position_in_set( &self, sticker: S, @@ -638,21 +639,21 @@ pub trait Requester { type DeleteStickerFromSet: Request; - /// For telegram documentation see [`DeleteStickerFromSet`] + /// For Telegram documentation see [`DeleteStickerFromSet`]. fn delete_sticker_from_set(&self, sticker: S) -> Self::DeleteStickerFromSet where S: Into; type SetStickerSetThumb: Request; - /// For telegram documentation see [`SetStickerSetThumb`] + /// For Telegram documentation see [`SetStickerSetThumb`]. fn set_sticker_set_thumb(&self, name: N, user_id: i32) -> Self::SetStickerSetThumb where N: Into; type SendInvoice: Request; - /// For telegram documentation see [`SendInvoice`] + /// For Telegram documentation see [`SendInvoice`]. #[allow(clippy::too_many_arguments)] fn send_invoice( &self, @@ -676,14 +677,14 @@ pub trait Requester { type AnswerShippingQuery: Request; - /// For telegram documentation see [`AnswerShippingQuery`] + /// For Telegram documentation see [`AnswerShippingQuery`]. fn answer_shipping_query(&self, shipping_query_id: S, ok: bool) -> Self::AnswerShippingQuery where S: Into; type AnswerPreCheckoutQuery: Request; - /// For telegram documentation see [`AnswerPreCheckoutQuery`] + /// For Telegram documentation see [`AnswerPreCheckoutQuery`]. fn answer_pre_checkout_query

( &self, pre_checkout_query_id: P, @@ -694,7 +695,7 @@ pub trait Requester { type SetPassportDataErrors: Request; - /// For telegram documentation see [`SetPassportDataErrors`] + /// For Telegram documentation see [`SetPassportDataErrors`]. fn set_passport_data_errors(&self, user_id: i32, errors: E) -> Self::SetPassportDataErrors where E: IntoIterator; diff --git a/src/requests/requester_ext.rs b/src/requests/requester_ext.rs index 57ef9a52..5f4b9e20 100644 --- a/src/requests/requester_ext.rs +++ b/src/requests/requester_ext.rs @@ -9,6 +9,7 @@ use crate::adaptors::AutoSend; #[cfg(feature = "throttle")] use crate::adaptors::throttle::{Limits, Throttle}; +/// Extensions methods for [`Requester`]. pub trait RequesterExt: Requester { /// Add `get_me` caching ability, see [`CacheMe`] for more. /// From 914ddd40bb62325771f4d5c10a86b5c278bcff2b Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 23 Dec 2020 22:46:31 +0600 Subject: [PATCH 165/755] Add #[allow(clippy::wrong_self_convention)] --- src/local_macros.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/local_macros.rs b/src/local_macros.rs index 51be0d62..146fedd3 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -243,6 +243,7 @@ macro_rules! impl_payload { stringify!($field), ") field." )] + #[allow(clippy::wrong_self_convention)] fn $field(mut self, value: T) -> Self where T: Into<$FTy>, @@ -263,6 +264,7 @@ macro_rules! impl_payload { stringify!($field), ") field." )] + #[allow(clippy::wrong_self_convention)] fn $field(mut self, value: T) -> Self where T: ::core::iter::IntoIterator::Item>, @@ -283,6 +285,7 @@ macro_rules! impl_payload { stringify!($field), ") field." )] + #[allow(clippy::wrong_self_convention)] fn $field(mut self, value: $FTy) -> Self { self.payload_mut().$field = Some(value); self @@ -300,6 +303,7 @@ macro_rules! impl_payload { stringify!($field), ") field." )] + #[allow(clippy::wrong_self_convention)] fn $field(mut self, value: T) -> Self where T: Into<$FTy>, @@ -320,6 +324,7 @@ macro_rules! impl_payload { stringify!($field), ") field." )] + #[allow(clippy::wrong_self_convention)] fn $field(mut self, value: T) -> Self where T: ::core::iter::IntoIterator::Item>, @@ -340,6 +345,7 @@ macro_rules! impl_payload { stringify!($field), ") field." )] + #[allow(clippy::wrong_self_convention)] fn $field(mut self, value: $FTy) -> Self { self.payload_mut().$field = value; self From dc7092fed56cb7c2566ec91e8cb4e9a23efa15c2 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 23 Dec 2020 23:14:58 +0600 Subject: [PATCH 166/755] Update src/requests/has_payload.rs Co-authored-by: Waffle Lapkin --- src/requests/has_payload.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/requests/has_payload.rs b/src/requests/has_payload.rs index 6c0c1abf..3a1135de 100644 --- a/src/requests/has_payload.rs +++ b/src/requests/has_payload.rs @@ -22,7 +22,7 @@ pub trait HasPayload // we wanted to use As{Mut,Ref} here, but they doesn't work // because of https://github.com/rust-lang/rust/issues/77010 { - /// A type of the payload contained. + /// The type of the payload contained. type Payload: Payload; /// Gain mutable access to the underlying payload. From 18c34a7f70c094dafd0ed86303b914e7c9d38d6f Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 23 Dec 2020 23:16:19 +0600 Subject: [PATCH 167/755] Update src/requests/payload.rs Co-authored-by: Waffle Lapkin --- src/requests/payload.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/requests/payload.rs b/src/requests/payload.rs index 8b885e96..d9940767 100644 --- a/src/requests/payload.rs +++ b/src/requests/payload.rs @@ -3,7 +3,7 @@ /// Simply speaking, structures implementing this trait represent arguments of /// a Telegram bot API method. /// -/// Also, this trait provides some additional information needed for sending a +/// Also, this trait provides some additional information needed to send a /// request to Telegram. #[cfg_attr(all(docsrs, feature = "nightly"), doc(spotlight))] pub trait Payload { From bb8148a3b30493fbbbfa2ce722ae025386fdf5ca Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 23 Dec 2020 23:16:42 +0600 Subject: [PATCH 168/755] Update src/requests/request.rs Co-authored-by: Waffle Lapkin --- src/requests/request.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/requests/request.rs b/src/requests/request.rs index b3f674f3..7e504803 100644 --- a/src/requests/request.rs +++ b/src/requests/request.rs @@ -23,7 +23,7 @@ pub trait Request: HasPayload { * use it before it's integrated in async/await */ - /// A type of an error that may happen while sending a request to Telegram. + /// The type of an error that may happen while sending a request to Telegram. type Err: std::error::Error + Send; /// A type of the future returned by the [`send`](Request::send) method. From f1e5f3f1b31d2c05010be4dba7023e4119ddb21f Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 23 Dec 2020 23:16:49 +0600 Subject: [PATCH 169/755] Update src/requests/request.rs Co-authored-by: Waffle Lapkin --- src/requests/request.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/requests/request.rs b/src/requests/request.rs index 7e504803..38ca6a61 100644 --- a/src/requests/request.rs +++ b/src/requests/request.rs @@ -26,7 +26,7 @@ pub trait Request: HasPayload { /// The type of an error that may happen while sending a request to Telegram. type Err: std::error::Error + Send; - /// A type of the future returned by the [`send`](Request::send) method. + /// The type of the future returned by the [`send`](Request::send) method. type Send: Future, Self::Err>> + Send; /// A type of the future returned by the [`send_ref`](Request::send_ref) From edc12d000848507261fbbfc7de0824bfe7b27b4f Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 23 Dec 2020 23:18:20 +0600 Subject: [PATCH 170/755] Update src/requests/payload.rs Co-authored-by: Waffle Lapkin --- src/requests/payload.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/requests/payload.rs b/src/requests/payload.rs index d9940767..34fa3d4b 100644 --- a/src/requests/payload.rs +++ b/src/requests/payload.rs @@ -7,7 +7,7 @@ /// request to Telegram. #[cfg_attr(all(docsrs, feature = "nightly"), doc(spotlight))] pub trait Payload { - /// A return type of a Telegram method. + /// The return type of a Telegram method. /// /// Note: it should not include `Result` wrappers (e.g. it should be simply /// [`Message`], [`True`] or something else). From 1188da84e325283ea96e25e39fa0ad3808fef5b3 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 23 Dec 2020 23:40:58 +0600 Subject: [PATCH 171/755] Small typo fixes --- src/adaptors.rs | 2 +- src/errors.rs | 6 +++--- src/requests/request.rs | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/adaptors.rs b/src/adaptors.rs index fc4c6583..51db702c 100644 --- a/src/adaptors.rs +++ b/src/adaptors.rs @@ -3,7 +3,7 @@ //! Bot adaptors are very similar to the [`Iterator`] adaptors: they are bots //! wrapping other bots to alter existing or add new functionality. //! -//! E.g. [`AutoSend`] allows `await`ing requests directly, no need to to use +//! E.g. [`AutoSend`] allows `await`ing requests directly, no need to use //! `.send()`. //! //! [`Requester`]: crate::requests::Requester diff --git a/src/errors.rs b/src/errors.rs index 1b183180..9c65dd6d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -8,7 +8,7 @@ use thiserror::Error; /// An error caused by downloading a file. #[derive(Debug, Error, From)] pub enum DownloadError { - /// Network error while downloading a file from Telegram. + /// A network error while downloading a file from Telegram. #[error("A network error: {0}")] NetworkError(#[source] reqwest::Error), @@ -20,10 +20,10 @@ pub enum DownloadError { /// An error caused by sending a request to Telegram. #[derive(Debug, Error)] pub enum RequestError { - /// Telegram API error. + /// A Telegram API error. #[error("A Telegram's error #{status_code}: {kind:?}")] ApiError { - /// Kind of an API error. + /// A kind of an API error. kind: ApiError, /// An HTTP code returned by Telegram, not very useful in practice. diff --git a/src/requests/request.rs b/src/requests/request.rs index b3f674f3..36f97976 100644 --- a/src/requests/request.rs +++ b/src/requests/request.rs @@ -31,9 +31,8 @@ pub trait Request: HasPayload { /// A type of the future returned by the [`send_ref`](Request::send_ref) /// method. - /// - /// Note: it intentionally forbids borrowing from `self` though anyway we - /// couldn't allow borrowing without GATs. + // Note: it intentionally forbids borrowing from `self` though anyway we + // couldn't allow borrowing without GATs. type SendRef: Future, Self::Err>> + Send; /// Send this request. From 109a5d89ab07575f4b926887a9b06b04e72173cd Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 23 Dec 2020 23:45:56 +0600 Subject: [PATCH 172/755] Satisfy rustfmt --- src/requests/request.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/requests/request.rs b/src/requests/request.rs index a1450837..abc48fa1 100644 --- a/src/requests/request.rs +++ b/src/requests/request.rs @@ -23,7 +23,8 @@ pub trait Request: HasPayload { * use it before it's integrated in async/await */ - /// The type of an error that may happen while sending a request to Telegram. + /// The type of an error that may happen while sending a request to + /// Telegram. type Err: std::error::Error + Send; /// The type of the future returned by the [`send`](Request::send) method. From 7cc79eda636482d1cd6afe7a1dcbe9c3909007d1 Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 28 Dec 2020 14:30:18 +0300 Subject: [PATCH 173/755] Fix API typos - Make `caption` args optional - Add missing game methods --- src/adaptors/auto_send.rs | 36 +++-- src/adaptors/cache_me.rs | 30 ++-- src/adaptors/parse_mode.rs | 28 ++-- src/adaptors/throttle.rs | 30 ++-- src/bot/api.rs | 132 ++++++++++-------- src/local_macros.rs | 74 ++++++---- src/payloads/add_sticker_to_set.rs | 2 +- src/payloads/answer_callback_query.rs | 4 +- src/payloads/answer_inline_query.rs | 4 +- src/payloads/answer_pre_checkout_query.rs | 2 +- src/payloads/answer_shipping_query.rs | 2 +- src/payloads/create_new_sticker_set.rs | 2 +- src/payloads/delete_chat_photo.rs | 2 +- src/payloads/delete_chat_sticker_set.rs | 2 +- src/payloads/delete_message.rs | 2 +- src/payloads/delete_sticker_from_set.rs | 2 +- src/payloads/delete_webhook.rs | 2 +- src/payloads/edit_message_caption.rs | 6 +- src/payloads/edit_message_caption_inline.rs | 6 +- src/payloads/edit_message_live_location.rs | 2 +- .../edit_message_live_location_inline.rs | 4 +- src/payloads/edit_message_media.rs | 2 +- src/payloads/edit_message_media_inline.rs | 2 +- src/payloads/edit_message_reply_markup.rs | 2 +- .../edit_message_reply_markup_inline.rs | 2 +- src/payloads/edit_message_text.rs | 2 +- src/payloads/edit_message_text_inline.rs | 2 +- src/payloads/export_chat_invite_link.rs | 2 +- src/payloads/forward_message.rs | 2 +- src/payloads/get_chat.rs | 2 +- src/payloads/get_chat_administrators.rs | 2 +- src/payloads/get_chat_member.rs | 2 +- src/payloads/get_chat_members_count.rs | 2 +- src/payloads/get_file.rs | 2 +- src/payloads/get_game_high_scores.rs | 24 ++++ src/payloads/get_me.rs | 2 +- src/payloads/get_my_commands.rs | 2 +- src/payloads/get_sticker_set.rs | 2 +- src/payloads/get_updates.rs | 2 +- src/payloads/get_user_profile_photos.rs | 2 +- src/payloads/get_webhook_info.rs | 2 +- src/payloads/kick_chat_member.rs | 2 +- src/payloads/leave_chat.rs | 2 +- src/payloads/mod.rs | 10 +- src/payloads/pin_chat_message.rs | 2 +- src/payloads/promote_chat_member.rs | 2 +- src/payloads/restrict_chat_member.rs | 2 +- src/payloads/send_animation.rs | 6 +- src/payloads/send_audio.rs | 6 +- src/payloads/send_chat_action.rs | 10 +- src/payloads/send_contact.rs | 2 +- src/payloads/send_dice.rs | 4 +- src/payloads/send_document.rs | 8 +- src/payloads/send_game.rs | 21 +++ src/payloads/send_invoice.rs | 2 +- src/payloads/send_location.rs | 4 +- src/payloads/send_media_group.rs | 2 +- src/payloads/send_message.rs | 4 +- src/payloads/send_photo.rs | 8 +- src/payloads/send_poll.rs | 2 +- src/payloads/send_sticker.rs | 2 +- src/payloads/send_venue.rs | 2 +- src/payloads/send_video.rs | 6 +- src/payloads/send_video_note.rs | 6 +- src/payloads/send_voice.rs | 8 +- .../set_chat_administrator_custom_title.rs | 2 +- src/payloads/set_chat_description.rs | 2 +- src/payloads/set_chat_permissions.rs | 2 +- src/payloads/set_chat_photo.rs | 2 +- src/payloads/set_chat_sticker_set.rs | 2 +- src/payloads/set_chat_title.rs | 2 +- src/payloads/set_game_score.rs | 33 +++++ src/payloads/set_game_score_inline.rs | 29 ++++ src/payloads/set_my_commands.rs | 2 +- src/payloads/set_passport_data_errors.rs | 2 +- src/payloads/set_sticker_position_in_set.rs | 2 +- src/payloads/set_sticker_set_thumb.rs | 2 +- src/payloads/set_webhook.rs | 2 +- src/payloads/setters.rs | 24 ++-- src/payloads/stop_message_live_location.rs | 4 +- .../stop_message_live_location_inline.rs | 4 +- src/payloads/stop_poll.rs | 2 +- src/payloads/unban_chat_member.rs | 2 +- src/payloads/unpin_chat_message.rs | 2 +- src/payloads/upload_sticker_file.rs | 2 +- src/requests/requester.rs | 119 +++++++++------- 86 files changed, 502 insertions(+), 300 deletions(-) create mode 100644 src/payloads/get_game_high_scores.rs create mode 100644 src/payloads/send_game.rs create mode 100644 src/payloads/set_game_score.rs create mode 100644 src/payloads/set_game_score_inline.rs diff --git a/src/adaptors/auto_send.rs b/src/adaptors/auto_send.rs index 54a85d87..890979ac 100644 --- a/src/adaptors/auto_send.rs +++ b/src/adaptors/auto_send.rs @@ -78,21 +78,27 @@ impl Requester for AutoSend { type Err = B::Err; requester_forward! { - get_me, send_message, get_updates, set_webhook, delete_webhook, get_webhook_info, - forward_message, send_photo, send_audio, send_document, send_video, - send_animation, send_voice, send_video_note, send_media_group, send_location, - edit_message_live_location, edit_message_live_location_inline, stop_message_live_location, - stop_message_live_location_inline, send_venue, send_contact, send_poll, send_dice, - send_chat_action, get_user_profile_photos, get_file, kick_chat_member, unban_chat_member, - restrict_chat_member, promote_chat_member, set_chat_administrator_custom_title, set_chat_permissions, - export_chat_invite_link, set_chat_photo, delete_chat_photo, set_chat_title, set_chat_description, - pin_chat_message, unpin_chat_message, leave_chat, get_chat, get_chat_administrators, get_chat_members_count, - get_chat_member, set_chat_sticker_set, delete_chat_sticker_set, answer_callback_query, set_my_commands, - get_my_commands, answer_inline_query, edit_message_text, edit_message_text_inline, edit_message_caption, - edit_message_caption_inline, edit_message_media, edit_message_media_inline, edit_message_reply_markup, - edit_message_reply_markup_inline, stop_poll, delete_message, send_sticker, get_sticker_set, upload_sticker_file, - create_new_sticker_set, add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, - set_sticker_set_thumb, send_invoice, answer_shipping_query, answer_pre_checkout_query, set_passport_data_errors => f, fty + get_me, send_message, get_updates, set_webhook, delete_webhook, + get_webhook_info, forward_message, send_photo, send_audio, send_document, + send_video, send_animation, send_voice, send_video_note, send_media_group, + send_location, edit_message_live_location, edit_message_live_location_inline, + stop_message_live_location, stop_message_live_location_inline, send_venue, + send_contact, send_poll, send_dice, send_chat_action, get_user_profile_photos, + get_file, kick_chat_member, unban_chat_member, restrict_chat_member, + promote_chat_member, set_chat_administrator_custom_title, set_chat_permissions, + export_chat_invite_link, set_chat_photo, delete_chat_photo, set_chat_title, + set_chat_description, pin_chat_message, unpin_chat_message, leave_chat, + get_chat, get_chat_administrators, get_chat_members_count, get_chat_member, + set_chat_sticker_set, delete_chat_sticker_set, answer_callback_query, + set_my_commands, get_my_commands, answer_inline_query, edit_message_text, + edit_message_text_inline, edit_message_caption, edit_message_caption_inline, + edit_message_media, edit_message_media_inline, edit_message_reply_markup, + edit_message_reply_markup_inline, stop_poll, delete_message, send_sticker, + get_sticker_set, upload_sticker_file, create_new_sticker_set, + add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, + set_sticker_set_thumb, send_invoice, answer_shipping_query, + answer_pre_checkout_query, set_passport_data_errors, send_game, + set_game_score, set_game_score_inline, get_game_high_scores => f, fty } } diff --git a/src/adaptors/cache_me.rs b/src/adaptors/cache_me.rs index 8f54a2ee..2a23a2ae 100644 --- a/src/adaptors/cache_me.rs +++ b/src/adaptors/cache_me.rs @@ -98,18 +98,24 @@ where get_updates, set_webhook, delete_webhook, get_webhook_info, forward_message, send_photo, send_audio, send_document, send_video, send_animation, send_voice, send_video_note, send_media_group, send_location, - edit_message_live_location, edit_message_live_location_inline, stop_message_live_location, - stop_message_live_location_inline, send_venue, send_contact, send_poll, send_dice, - send_chat_action, get_user_profile_photos, get_file, kick_chat_member, unban_chat_member, - restrict_chat_member, promote_chat_member, set_chat_administrator_custom_title, set_chat_permissions, - export_chat_invite_link, set_chat_photo, delete_chat_photo, set_chat_title, set_chat_description, - pin_chat_message, unpin_chat_message, leave_chat, get_chat, get_chat_administrators, get_chat_members_count, - get_chat_member, set_chat_sticker_set, delete_chat_sticker_set, answer_callback_query, set_my_commands, - get_my_commands, answer_inline_query, edit_message_text, edit_message_text_inline, edit_message_caption, - edit_message_caption_inline, edit_message_media, edit_message_media_inline, edit_message_reply_markup, - edit_message_reply_markup_inline, stop_poll, delete_message, send_sticker, get_sticker_set, upload_sticker_file, - create_new_sticker_set, add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, - set_sticker_set_thumb, send_invoice, answer_shipping_query, answer_pre_checkout_query, set_passport_data_errors => f, fty + edit_message_live_location, edit_message_live_location_inline, + stop_message_live_location, stop_message_live_location_inline, send_venue, + send_contact, send_poll, send_dice, send_chat_action, get_user_profile_photos, + get_file, kick_chat_member, unban_chat_member, restrict_chat_member, + promote_chat_member, set_chat_administrator_custom_title, set_chat_permissions, + export_chat_invite_link, set_chat_photo, delete_chat_photo, set_chat_title, + set_chat_description, pin_chat_message, unpin_chat_message, leave_chat, + get_chat, get_chat_administrators, get_chat_members_count,get_chat_member, + set_chat_sticker_set, delete_chat_sticker_set, answer_callback_query, + set_my_commands, get_my_commands, answer_inline_query, edit_message_text, + edit_message_text_inline, edit_message_caption, edit_message_caption_inline, + edit_message_media, edit_message_media_inline, edit_message_reply_markup, + edit_message_reply_markup_inline, stop_poll, delete_message, send_sticker, + get_sticker_set, upload_sticker_file, create_new_sticker_set, + add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, + set_sticker_set_thumb, send_invoice, answer_shipping_query, + answer_pre_checkout_query, set_passport_data_errors, send_game, + set_game_score, set_game_score_inline, get_game_high_scores => f, fty } } diff --git a/src/adaptors/parse_mode.rs b/src/adaptors/parse_mode.rs index 5057a5f1..df7a612b 100644 --- a/src/adaptors/parse_mode.rs +++ b/src/adaptors/parse_mode.rs @@ -93,17 +93,23 @@ impl Requester for DefaultParseMode { requester_forward! { get_me, get_updates, set_webhook, delete_webhook, get_webhook_info, forward_message, send_video_note, send_media_group, send_location, - edit_message_live_location, edit_message_live_location_inline, stop_message_live_location, - stop_message_live_location_inline, send_venue, send_contact, send_dice, - send_chat_action, get_user_profile_photos, get_file, kick_chat_member, unban_chat_member, - restrict_chat_member, promote_chat_member, set_chat_administrator_custom_title, set_chat_permissions, - export_chat_invite_link, set_chat_photo, delete_chat_photo, set_chat_title, set_chat_description, - pin_chat_message, unpin_chat_message, leave_chat, get_chat, get_chat_administrators, get_chat_members_count, - get_chat_member, set_chat_sticker_set, delete_chat_sticker_set, answer_callback_query, set_my_commands, - get_my_commands, answer_inline_query, edit_message_media, edit_message_media_inline, edit_message_reply_markup, - edit_message_reply_markup_inline, stop_poll, delete_message, send_sticker, get_sticker_set, upload_sticker_file, - create_new_sticker_set, add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, - set_sticker_set_thumb, send_invoice, answer_shipping_query, answer_pre_checkout_query, set_passport_data_errors => fid, fty + edit_message_live_location, edit_message_live_location_inline, + stop_message_live_location, stop_message_live_location_inline, send_venue, + send_contact, send_dice, send_chat_action, get_user_profile_photos, + get_file, kick_chat_member, unban_chat_member, restrict_chat_member, + promote_chat_member, set_chat_administrator_custom_title, set_chat_permissions, + export_chat_invite_link, set_chat_photo, delete_chat_photo, set_chat_title, + set_chat_description, pin_chat_message, unpin_chat_message, leave_chat, + get_chat, get_chat_administrators, get_chat_members_count, get_chat_member, + set_chat_sticker_set, delete_chat_sticker_set, answer_callback_query, + set_my_commands, get_my_commands, answer_inline_query, edit_message_media, + edit_message_media_inline, edit_message_reply_markup, + edit_message_reply_markup_inline, stop_poll, delete_message, + send_sticker, get_sticker_set, upload_sticker_file, create_new_sticker_set, + add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, + set_sticker_set_thumb, send_invoice, answer_shipping_query, + answer_pre_checkout_query, set_passport_data_errors, send_game, set_game_score, + set_game_score_inline, get_game_high_scores => fid, fty } } diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index e7e2250c..db26038f 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -443,17 +443,25 @@ where } requester_forward! { - get_me, get_updates, set_webhook, delete_webhook, get_webhook_info, edit_message_live_location, edit_message_live_location_inline, stop_message_live_location, - stop_message_live_location_inline, send_chat_action, get_user_profile_photos, get_file, kick_chat_member, unban_chat_member, - restrict_chat_member, promote_chat_member, set_chat_administrator_custom_title, set_chat_permissions, - export_chat_invite_link, set_chat_photo, delete_chat_photo, set_chat_title, set_chat_description, - pin_chat_message, unpin_chat_message, leave_chat, get_chat, get_chat_administrators, get_chat_members_count, - get_chat_member, set_chat_sticker_set, delete_chat_sticker_set, answer_callback_query, set_my_commands, - get_my_commands, answer_inline_query, edit_message_text, edit_message_text_inline, edit_message_caption, - edit_message_caption_inline, edit_message_media, edit_message_media_inline, edit_message_reply_markup, - edit_message_reply_markup_inline, stop_poll, delete_message, get_sticker_set, upload_sticker_file, - create_new_sticker_set, add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, - set_sticker_set_thumb, answer_shipping_query, answer_pre_checkout_query, set_passport_data_errors => fid, ftyid + get_me, get_updates, set_webhook, delete_webhook, get_webhook_info, + edit_message_live_location, edit_message_live_location_inline, + stop_message_live_location, stop_message_live_location_inline, + send_chat_action, get_user_profile_photos, get_file, kick_chat_member, + unban_chat_member, restrict_chat_member, promote_chat_member, + set_chat_administrator_custom_title, set_chat_permissions, + export_chat_invite_link, set_chat_photo, delete_chat_photo, set_chat_title, + set_chat_description, pin_chat_message, unpin_chat_message, leave_chat, + get_chat, get_chat_administrators, get_chat_members_count, + get_chat_member, set_chat_sticker_set, delete_chat_sticker_set, + answer_callback_query, set_my_commands, get_my_commands, answer_inline_query, + edit_message_text, edit_message_text_inline, edit_message_caption, + edit_message_caption_inline, edit_message_media, edit_message_media_inline, + edit_message_reply_markup, edit_message_reply_markup_inline, stop_poll, + delete_message, get_sticker_set, upload_sticker_file, create_new_sticker_set, + add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, + set_sticker_set_thumb, answer_shipping_query, answer_pre_checkout_query, + set_passport_data_errors, send_game, set_game_score, set_game_score_inline, + get_game_high_scores => fid, ftyid } } diff --git a/src/bot/api.rs b/src/bot/api.rs index 560efd01..01463afa 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -76,78 +76,56 @@ impl Requester for Bot { type SendPhoto = MultipartRequest; - fn send_photo(&self, chat_id: Ch, photo: InputFile, caption: Ca) -> Self::SendPhoto + fn send_photo(&self, chat_id: C, photo: InputFile) -> Self::SendPhoto where - Ch: Into, - Ca: Into, + C: Into, { - Self::SendPhoto::new(self.clone(), payloads::SendPhoto::new(chat_id, photo, caption)) + Self::SendPhoto::new(self.clone(), payloads::SendPhoto::new(chat_id, photo)) } type SendAudio = MultipartRequest; - fn send_audio(&self, chat_id: Ch, audio: InputFile, caption: Ca) -> Self::SendAudio + fn send_audio(&self, chat_id: C, audio: InputFile) -> Self::SendAudio where - Ch: Into, - Ca: Into, + C: Into, { - Self::SendAudio::new(self.clone(), payloads::SendAudio::new(chat_id, audio, caption)) + Self::SendAudio::new(self.clone(), payloads::SendAudio::new(chat_id, audio)) } type SendDocument = MultipartRequest; - fn send_document( - &self, - chat_id: Ch, - document: InputFile, - caption: Ca, - ) -> Self::SendDocument + fn send_document(&self, chat_id: C, document: InputFile) -> Self::SendDocument where - Ch: Into, - Ca: Into, + C: Into, { - Self::SendDocument::new( - self.clone(), - payloads::SendDocument::new(chat_id, document, caption), - ) + Self::SendDocument::new(self.clone(), payloads::SendDocument::new(chat_id, document)) } type SendVideo = MultipartRequest; - fn send_video(&self, chat_id: Ch, video: InputFile, caption: Ca) -> Self::SendVideo + fn send_video(&self, chat_id: C, video: InputFile) -> Self::SendVideo where - Ch: Into, - Ca: Into, + C: Into, { - Self::SendVideo::new(self.clone(), payloads::SendVideo::new(chat_id, video, caption)) + Self::SendVideo::new(self.clone(), payloads::SendVideo::new(chat_id, video)) } type SendAnimation = MultipartRequest; - fn send_animation( - &self, - chat_id: Ch, - animation: InputFile, - caption: Ca, - ) -> Self::SendAnimation + fn send_animation(&self, chat_id: C, animation: InputFile) -> Self::SendAnimation where - Ch: Into, - Ca: Into, + C: Into, { - Self::SendAnimation::new( - self.clone(), - payloads::SendAnimation::new(chat_id, animation, caption), - ) + Self::SendAnimation::new(self.clone(), payloads::SendAnimation::new(chat_id, animation)) } type SendVoice = MultipartRequest; - fn send_voice(&self, chat_id: Ch, voice: InputFile, caption: Ca) -> Self::SendVoice + fn send_voice(&self, chat_id: C, voice: InputFile) -> Self::SendVoice where - Ch: Into, - Ca: Into, + C: Into, { - Self::SendVoice::new(self.clone(), payloads::SendVoice::new(chat_id, voice, caption)) + Self::SendVoice::new(self.clone(), payloads::SendVoice::new(chat_id, voice)) } type SendVideoNote = MultipartRequest; @@ -634,36 +612,25 @@ impl Requester for Bot { type EditMessageCaption = JsonRequest; - fn edit_message_caption( - &self, - chat_id: Ch, - message_id: i32, - caption: Ca, - ) -> Self::EditMessageCaption + fn edit_message_caption(&self, chat_id: C, message_id: i32) -> Self::EditMessageCaption where - Ch: Into, - Ca: Into, + C: Into, { Self::EditMessageCaption::new( self.clone(), - payloads::EditMessageCaption::new(chat_id, message_id, caption), + payloads::EditMessageCaption::new(chat_id, message_id), ) } type EditMessageCaptionInline = JsonRequest; - fn edit_message_caption_inline( - &self, - inline_message_id: I, - caption: C, - ) -> Self::EditMessageCaptionInline + fn edit_message_caption_inline(&self, inline_message_id: I) -> Self::EditMessageCaptionInline where I: Into, - C: Into, { Self::EditMessageCaptionInline::new( self.clone(), - payloads::EditMessageCaptionInline::new(inline_message_id, caption), + payloads::EditMessageCaptionInline::new(inline_message_id), ) } @@ -929,4 +896,57 @@ impl Requester for Bot { payloads::SetPassportDataErrors::new(user_id, errors), ) } + + type SendGame = JsonRequest; + + fn send_game(&self, chat_id: u32, game_short_name: G) -> Self::SendGame + where + G: Into, + { + Self::SendGame::new(self.clone(), payloads::SendGame::new(chat_id, game_short_name)) + } + + type SetGameScore = JsonRequest; + + fn set_game_score( + &self, + user_id: u32, + score: u64, + chat_id: u32, + message_id: i64, + ) -> Self::SetGameScore { + Self::SetGameScore::new( + self.clone(), + payloads::SetGameScore::new(user_id, score, chat_id, message_id), + ) + } + + type SetGameScoreInline = JsonRequest; + + fn set_game_score_inline( + &self, + user_id: u32, + score: u64, + inline_message_id: I, + ) -> Self::SetGameScoreInline + where + I: Into, + { + Self::SetGameScoreInline::new( + self.clone(), + payloads::SetGameScoreInline::new(user_id, score, inline_message_id), + ) + } + + type GetGameHighScores = JsonRequest; + + fn get_game_high_scores(&self, user_id: u32, target: T) -> Self::GetGameHighScores + where + T: Into, + { + Self::GetGameHighScores::new( + self.clone(), + payloads::GetGameHighScores::new(user_id, target), + ) + } } diff --git a/src/local_macros.rs b/src/local_macros.rs index 146fedd3..25cffd7f 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -373,7 +373,7 @@ macro_rules! impl_payload { } #[macro_use] -// This macro is auto generated by `cg` (a8fa55a). +// This macro is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS MACRO**, // edit `cg` instead. macro_rules! requester_forward { @@ -447,55 +447,49 @@ macro_rules! requester_forward { (@method send_photo $body:ident $ty:ident) => { type SendPhoto = $ty![SendPhoto]; - fn send_photo(&self, chat_id: Ch, photo: InputFile, caption: Ca) -> Self::SendPhoto where Ch: Into, - Ca: Into { + fn send_photo(&self, chat_id: C, photo: InputFile) -> Self::SendPhoto where C: Into { let this = self; - $body!(send_photo this (chat_id: Ch, photo: InputFile, caption: Ca)) + $body!(send_photo this (chat_id: C, photo: InputFile)) } }; (@method send_audio $body:ident $ty:ident) => { type SendAudio = $ty![SendAudio]; - fn send_audio(&self, chat_id: Ch, audio: InputFile, caption: Ca) -> Self::SendAudio where Ch: Into, - Ca: Into { + fn send_audio(&self, chat_id: C, audio: InputFile) -> Self::SendAudio where C: Into { let this = self; - $body!(send_audio this (chat_id: Ch, audio: InputFile, caption: Ca)) + $body!(send_audio this (chat_id: C, audio: InputFile)) } }; (@method send_document $body:ident $ty:ident) => { type SendDocument = $ty![SendDocument]; - fn send_document(&self, chat_id: Ch, document: InputFile, caption: Ca) -> Self::SendDocument where Ch: Into, - Ca: Into { + fn send_document(&self, chat_id: C, document: InputFile) -> Self::SendDocument where C: Into { let this = self; - $body!(send_document this (chat_id: Ch, document: InputFile, caption: Ca)) + $body!(send_document this (chat_id: C, document: InputFile)) } }; (@method send_video $body:ident $ty:ident) => { type SendVideo = $ty![SendVideo]; - fn send_video(&self, chat_id: Ch, video: InputFile, caption: Ca) -> Self::SendVideo where Ch: Into, - Ca: Into { + fn send_video(&self, chat_id: C, video: InputFile) -> Self::SendVideo where C: Into { let this = self; - $body!(send_video this (chat_id: Ch, video: InputFile, caption: Ca)) + $body!(send_video this (chat_id: C, video: InputFile)) } }; (@method send_animation $body:ident $ty:ident) => { type SendAnimation = $ty![SendAnimation]; - fn send_animation(&self, chat_id: Ch, animation: InputFile, caption: Ca) -> Self::SendAnimation where Ch: Into, - Ca: Into { + fn send_animation(&self, chat_id: C, animation: InputFile) -> Self::SendAnimation where C: Into { let this = self; - $body!(send_animation this (chat_id: Ch, animation: InputFile, caption: Ca)) + $body!(send_animation this (chat_id: C, animation: InputFile)) } }; (@method send_voice $body:ident $ty:ident) => { type SendVoice = $ty![SendVoice]; - fn send_voice(&self, chat_id: Ch, voice: InputFile, caption: Ca) -> Self::SendVoice where Ch: Into, - Ca: Into { + fn send_voice(&self, chat_id: C, voice: InputFile) -> Self::SendVoice where C: Into { let this = self; - $body!(send_voice this (chat_id: Ch, voice: InputFile, caption: Ca)) + $body!(send_voice this (chat_id: C, voice: InputFile)) } }; (@method send_video_note $body:ident $ty:ident) => { @@ -832,19 +826,17 @@ macro_rules! requester_forward { (@method edit_message_caption $body:ident $ty:ident) => { type EditMessageCaption = $ty![EditMessageCaption]; - fn edit_message_caption(&self, chat_id: Ch, message_id: i32, caption: Ca) -> Self::EditMessageCaption where Ch: Into, - Ca: Into { + fn edit_message_caption(&self, chat_id: C, message_id: i32) -> Self::EditMessageCaption where C: Into { let this = self; - $body!(edit_message_caption this (chat_id: Ch, message_id: i32, caption: Ca)) + $body!(edit_message_caption this (chat_id: C, message_id: i32)) } }; (@method edit_message_caption_inline $body:ident $ty:ident) => { type EditMessageCaptionInline = $ty![EditMessageCaptionInline]; - fn edit_message_caption_inline(&self, inline_message_id: I, caption: C) -> Self::EditMessageCaptionInline where I: Into, - C: Into { + fn edit_message_caption_inline(&self, inline_message_id: I) -> Self::EditMessageCaptionInline where I: Into { let this = self; - $body!(edit_message_caption_inline this (inline_message_id: I, caption: C)) + $body!(edit_message_caption_inline this (inline_message_id: I)) } }; (@method edit_message_media $body:ident $ty:ident) => { @@ -1000,6 +992,38 @@ macro_rules! requester_forward { $body!(set_passport_data_errors this (user_id: i32, errors: E)) } }; + (@method send_game $body:ident $ty:ident) => { + type SendGame = $ty![SendGame]; + + fn send_game(&self, chat_id: u32, game_short_name: G) -> Self::SendGame where G: Into { + let this = self; + $body!(send_game this (chat_id: u32, game_short_name: G)) + } + }; + (@method set_game_score $body:ident $ty:ident) => { + type SetGameScore = $ty![SetGameScore]; + + fn set_game_score(&self, user_id: u32, score: u64, chat_id: u32, message_id: i64) -> Self::SetGameScore { + let this = self; + $body!(set_game_score this (user_id: u32, score: u64, chat_id: u32, message_id: i64)) + } + }; + (@method set_game_score_inline $body:ident $ty:ident) => { + type SetGameScoreInline = $ty![SetGameScoreInline]; + + fn set_game_score_inline(&self, user_id: u32, score: u64, inline_message_id: I) -> Self::SetGameScoreInline where I: Into { + let this = self; + $body!(set_game_score_inline this (user_id: u32, score: u64, inline_message_id: I)) + } + }; + (@method get_game_high_scores $body:ident $ty:ident) => { + type GetGameHighScores = $ty![GetGameHighScores]; + + fn get_game_high_scores(&self, user_id: u32, target: T) -> Self::GetGameHighScores where T: Into { + let this = self; + $body!(get_game_high_scores this (user_id: u32, target: T)) + } + }; } #[macro_use] diff --git a/src/payloads/add_sticker_to_set.rs b/src/payloads/add_sticker_to_set.rs index 7836bd43..1a24bf81 100644 --- a/src/payloads/add_sticker_to_set.rs +++ b/src/payloads/add_sticker_to_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/answer_callback_query.rs b/src/payloads/answer_callback_query.rs index fda34ae4..1ff7f262 100644 --- a/src/payloads/answer_callback_query.rs +++ b/src/payloads/answer_callback_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -27,8 +27,8 @@ impl_payload! { /// /// Otherwise, you may use links like `t.me/your\_bot?start=XXXX` that open your bot with a parameter. /// - /// [@Botfather]: https://t.me/botfather /// [`Game`]: crate::types::Game + /// [@Botfather]: https://t.me/botfather /// [callback_game]: https://core.telegram.org/bots/api#inlinekeyboardbutton pub url: String [into], /// The maximum amount of time in seconds that the result of the callback query may be cached client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0. diff --git a/src/payloads/answer_inline_query.rs b/src/payloads/answer_inline_query.rs index 7032695b..7c970a97 100644 --- a/src/payloads/answer_inline_query.rs +++ b/src/payloads/answer_inline_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -28,8 +28,8 @@ impl_payload! { /// /// _Example_: An inline bot that sends YouTube videos can ask the user to connect the bot to their YouTube account to adapt search results accordingly. To do this, it displays a 'Connect your YouTube account' button above the results, or even before showing any. The user presses the button, switches to a private chat with the bot and, in doing so, passes a start parameter that instructs the bot to return an oauth link. Once done, the bot can offer a [switch_inline] button so that the user can easily return to the chat where they wanted to use the bot's inline capabilities. /// - /// [switch_inline]: https://core.telegram.org/bots/api#inlinekeyboardmarkup /// [Deep-linking]: https://core.telegram.org/bots#deep-linking + /// [switch_inline]: https://core.telegram.org/bots/api#inlinekeyboardmarkup pub switch_pm_parameter: String [into], } } diff --git a/src/payloads/answer_pre_checkout_query.rs b/src/payloads/answer_pre_checkout_query.rs index 821bb4ae..ec884ba9 100644 --- a/src/payloads/answer_pre_checkout_query.rs +++ b/src/payloads/answer_pre_checkout_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/answer_shipping_query.rs b/src/payloads/answer_shipping_query.rs index a4fc80f7..b00b29c1 100644 --- a/src/payloads/answer_shipping_query.rs +++ b/src/payloads/answer_shipping_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/create_new_sticker_set.rs b/src/payloads/create_new_sticker_set.rs index 93f945c4..1b74097a 100644 --- a/src/payloads/create_new_sticker_set.rs +++ b/src/payloads/create_new_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_chat_photo.rs b/src/payloads/delete_chat_photo.rs index dc447a48..23165b38 100644 --- a/src/payloads/delete_chat_photo.rs +++ b/src/payloads/delete_chat_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_chat_sticker_set.rs b/src/payloads/delete_chat_sticker_set.rs index fef2f48b..ba87b16a 100644 --- a/src/payloads/delete_chat_sticker_set.rs +++ b/src/payloads/delete_chat_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_message.rs b/src/payloads/delete_message.rs index cc94d937..cf31bf93 100644 --- a/src/payloads/delete_message.rs +++ b/src/payloads/delete_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_sticker_from_set.rs b/src/payloads/delete_sticker_from_set.rs index ec8ca0fb..b432c6ce 100644 --- a/src/payloads/delete_sticker_from_set.rs +++ b/src/payloads/delete_sticker_from_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_webhook.rs b/src/payloads/delete_webhook.rs index 3c6906af..ebdd2823 100644 --- a/src/payloads/delete_webhook.rs +++ b/src/payloads/delete_webhook.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_caption.rs b/src/payloads/edit_message_caption.rs index aafa4f69..3b626d28 100644 --- a/src/payloads/edit_message_caption.rs +++ b/src/payloads/edit_message_caption.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -16,10 +16,10 @@ impl_payload! { pub chat_id: ChatId [into], /// Identifier of the message to edit pub message_id: i32, - /// New caption of the message, 0-1024 characters after entities parsing - pub caption: String [into], } optional { + /// New caption of the message, 0-1024 characters after entities parsing + pub caption: String [into], /// Mode for parsing entities in the message text. See [formatting options] for more details. /// /// [formatting options]: https://core.telegram.org/bots/api#formatting-options diff --git a/src/payloads/edit_message_caption_inline.rs b/src/payloads/edit_message_caption_inline.rs index fe94044f..565b7486 100644 --- a/src/payloads/edit_message_caption_inline.rs +++ b/src/payloads/edit_message_caption_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -14,10 +14,10 @@ impl_payload! { required { /// Identifier of the inline message pub inline_message_id: String [into], - /// New caption of the message, 0-1024 characters after entities parsing - pub caption: String [into], } optional { + /// New caption of the message, 0-1024 characters after entities parsing + pub caption: String [into], /// Mode for parsing entities in the message text. See [formatting options] for more details. /// /// [formatting options]: https://core.telegram.org/bots/api#formatting-options diff --git a/src/payloads/edit_message_live_location.rs b/src/payloads/edit_message_live_location.rs index 727f46d4..c183cb0a 100644 --- a/src/payloads/edit_message_live_location.rs +++ b/src/payloads/edit_message_live_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_live_location_inline.rs b/src/payloads/edit_message_live_location_inline.rs index 41bb41a8..79389c4a 100644 --- a/src/payloads/edit_message_live_location_inline.rs +++ b/src/payloads/edit_message_live_location_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -24,8 +24,8 @@ impl_payload! { optional { /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/edit_message_media.rs b/src/payloads/edit_message_media.rs index 5e687339..f85aa27f 100644 --- a/src/payloads/edit_message_media.rs +++ b/src/payloads/edit_message_media.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_media_inline.rs b/src/payloads/edit_message_media_inline.rs index b5247819..1a3b5d28 100644 --- a/src/payloads/edit_message_media_inline.rs +++ b/src/payloads/edit_message_media_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_reply_markup.rs b/src/payloads/edit_message_reply_markup.rs index 4deafeb4..cc7b2777 100644 --- a/src/payloads/edit_message_reply_markup.rs +++ b/src/payloads/edit_message_reply_markup.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_reply_markup_inline.rs b/src/payloads/edit_message_reply_markup_inline.rs index 0999db8a..a34db1a6 100644 --- a/src/payloads/edit_message_reply_markup_inline.rs +++ b/src/payloads/edit_message_reply_markup_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_text.rs b/src/payloads/edit_message_text.rs index 00a8aa72..fe475bce 100644 --- a/src/payloads/edit_message_text.rs +++ b/src/payloads/edit_message_text.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_text_inline.rs b/src/payloads/edit_message_text_inline.rs index d498d71c..758de475 100644 --- a/src/payloads/edit_message_text_inline.rs +++ b/src/payloads/edit_message_text_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/export_chat_invite_link.rs b/src/payloads/export_chat_invite_link.rs index d1d8ce6d..44b5ec3d 100644 --- a/src/payloads/export_chat_invite_link.rs +++ b/src/payloads/export_chat_invite_link.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/forward_message.rs b/src/payloads/forward_message.rs index 336004e6..ce11ac85 100644 --- a/src/payloads/forward_message.rs +++ b/src/payloads/forward_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat.rs b/src/payloads/get_chat.rs index ce6d6b87..c110722c 100644 --- a/src/payloads/get_chat.rs +++ b/src/payloads/get_chat.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat_administrators.rs b/src/payloads/get_chat_administrators.rs index 169987f2..10cf24e4 100644 --- a/src/payloads/get_chat_administrators.rs +++ b/src/payloads/get_chat_administrators.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat_member.rs b/src/payloads/get_chat_member.rs index 22eb2aaf..f5e80f55 100644 --- a/src/payloads/get_chat_member.rs +++ b/src/payloads/get_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat_members_count.rs b/src/payloads/get_chat_members_count.rs index e353ff6d..0fd2c05e 100644 --- a/src/payloads/get_chat_members_count.rs +++ b/src/payloads/get_chat_members_count.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_file.rs b/src/payloads/get_file.rs index 3dfdcfac..47d9225c 100644 --- a/src/payloads/get_file.rs +++ b/src/payloads/get_file.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_game_high_scores.rs b/src/payloads/get_game_high_scores.rs new file mode 100644 index 00000000..2678043a --- /dev/null +++ b/src/payloads/get_game_high_scores.rs @@ -0,0 +1,24 @@ +// This file is auto generated by `cg` (9a82143). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::{TargetMessage, True}; + +impl_payload! { + /// Use this method to get data for high score tables. Will return the score of the specified user and several of their neighbors in a game. On success, returns an Array of [`GameHighScore`] objects. + /// + /// > This method will currently return scores for the target user, plus two of their closest neighbors on each side. Will also return the top three users if the user and his neighbors are not among them. Please note that this behavior is subject to change. + /// + /// [`GameHighScore`]: crate::types::GameHighScore + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub GetGameHighScores (GetGameHighScoresSetters) => True { + required { + /// User identifier + pub user_id: u32, + /// Target message + #[serde(flatten)] + pub target: TargetMessage [into], + } + } +} diff --git a/src/payloads/get_me.rs b/src/payloads/get_me.rs index 86cae760..76e97ab7 100644 --- a/src/payloads/get_me.rs +++ b/src/payloads/get_me.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_my_commands.rs b/src/payloads/get_my_commands.rs index 405b6847..bfa35ecb 100644 --- a/src/payloads/get_my_commands.rs +++ b/src/payloads/get_my_commands.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_sticker_set.rs b/src/payloads/get_sticker_set.rs index 10ff4917..35466950 100644 --- a/src/payloads/get_sticker_set.rs +++ b/src/payloads/get_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_updates.rs b/src/payloads/get_updates.rs index 514002da..3dba9e34 100644 --- a/src/payloads/get_updates.rs +++ b/src/payloads/get_updates.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_user_profile_photos.rs b/src/payloads/get_user_profile_photos.rs index 590096e2..9ae52286 100644 --- a/src/payloads/get_user_profile_photos.rs +++ b/src/payloads/get_user_profile_photos.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_webhook_info.rs b/src/payloads/get_webhook_info.rs index c342b2a0..846c907a 100644 --- a/src/payloads/get_webhook_info.rs +++ b/src/payloads/get_webhook_info.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/kick_chat_member.rs b/src/payloads/kick_chat_member.rs index 835f33d3..81f5014c 100644 --- a/src/payloads/kick_chat_member.rs +++ b/src/payloads/kick_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/leave_chat.rs b/src/payloads/leave_chat.rs index 4c4ee3e2..27af51a1 100644 --- a/src/payloads/leave_chat.rs +++ b/src/payloads/leave_chat.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/mod.rs b/src/payloads/mod.rs index 42b90fe7..abfebc1b 100644 --- a/src/payloads/mod.rs +++ b/src/payloads/mod.rs @@ -11,7 +11,7 @@ /// namespace. pub mod setters; -// This block is auto generated by `cg` (878e847). +// This block is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS BLOCK**, // edit `cg` instead. mod add_sticker_to_set; @@ -42,6 +42,7 @@ mod get_chat_administrators; mod get_chat_member; mod get_chat_members_count; mod get_file; +mod get_game_high_scores; mod get_me; mod get_my_commands; mod get_sticker_set; @@ -59,6 +60,7 @@ mod send_chat_action; mod send_contact; mod send_dice; mod send_document; +mod send_game; mod send_invoice; mod send_location; mod send_media_group; @@ -76,6 +78,8 @@ mod set_chat_permissions; mod set_chat_photo; mod set_chat_sticker_set; mod set_chat_title; +mod set_game_score; +mod set_game_score_inline; mod set_my_commands; mod set_passport_data_errors; mod set_sticker_position_in_set; @@ -120,6 +124,7 @@ pub use get_chat_administrators::{GetChatAdministrators, GetChatAdministratorsSe pub use get_chat_member::{GetChatMember, GetChatMemberSetters}; pub use get_chat_members_count::{GetChatMembersCount, GetChatMembersCountSetters}; pub use get_file::{GetFile, GetFileSetters}; +pub use get_game_high_scores::{GetGameHighScores, GetGameHighScoresSetters}; pub use get_me::{GetMe, GetMeSetters}; pub use get_my_commands::{GetMyCommands, GetMyCommandsSetters}; pub use get_sticker_set::{GetStickerSet, GetStickerSetSetters}; @@ -137,6 +142,7 @@ pub use send_chat_action::{SendChatAction, SendChatActionSetters}; pub use send_contact::{SendContact, SendContactSetters}; pub use send_dice::{SendDice, SendDiceSetters}; pub use send_document::{SendDocument, SendDocumentSetters}; +pub use send_game::{SendGame, SendGameSetters}; pub use send_invoice::{SendInvoice, SendInvoiceSetters}; pub use send_location::{SendLocation, SendLocationSetters}; pub use send_media_group::{SendMediaGroup, SendMediaGroupSetters}; @@ -156,6 +162,8 @@ pub use set_chat_permissions::{SetChatPermissions, SetChatPermissionsSetters}; pub use set_chat_photo::{SetChatPhoto, SetChatPhotoSetters}; pub use set_chat_sticker_set::{SetChatStickerSet, SetChatStickerSetSetters}; pub use set_chat_title::{SetChatTitle, SetChatTitleSetters}; +pub use set_game_score::{SetGameScore, SetGameScoreSetters}; +pub use set_game_score_inline::{SetGameScoreInline, SetGameScoreInlineSetters}; pub use set_my_commands::{SetMyCommands, SetMyCommandsSetters}; pub use set_passport_data_errors::{SetPassportDataErrors, SetPassportDataErrorsSetters}; pub use set_sticker_position_in_set::{SetStickerPositionInSet, SetStickerPositionInSetSetters}; diff --git a/src/payloads/pin_chat_message.rs b/src/payloads/pin_chat_message.rs index 73b03b5f..b3d31f1b 100644 --- a/src/payloads/pin_chat_message.rs +++ b/src/payloads/pin_chat_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/promote_chat_member.rs b/src/payloads/promote_chat_member.rs index 71c8cbeb..6d259b4a 100644 --- a/src/payloads/promote_chat_member.rs +++ b/src/payloads/promote_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/restrict_chat_member.rs b/src/payloads/restrict_chat_member.rs index 81aa68ff..a168f493 100644 --- a/src/payloads/restrict_chat_member.rs +++ b/src/payloads/restrict_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_animation.rs b/src/payloads/send_animation.rs index 168c5681..364f821e 100644 --- a/src/payloads/send_animation.rs +++ b/src/payloads/send_animation.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -18,8 +18,6 @@ impl_payload! { /// /// [More info on Sending Files Âģ]: crate::types::InputFile pub animation: InputFile, - /// Animation caption (may also be used when resending videos by _file\_id_), 0-1024 characters after entities parsing - pub caption: String [into], } optional { /// Duration of the animation in seconds @@ -32,6 +30,8 @@ impl_payload! { /// /// [More info on Sending Files Âģ]: crate::types::InputFile pub thumb: InputFile, + /// Animation caption (may also be used when resending videos by _file\_id_), 0-1024 characters after entities parsing + pub caption: String [into], /// Mode for parsing entities in the animation caption. See [formatting options] for more details. /// /// [formatting options]: https://core.telegram.org/bots/api#formatting-options diff --git a/src/payloads/send_audio.rs b/src/payloads/send_audio.rs index 6c7e636c..87cb3de6 100644 --- a/src/payloads/send_audio.rs +++ b/src/payloads/send_audio.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -21,10 +21,10 @@ impl_payload! { /// /// [More info on Sending Files Âģ]: crate::types::InputFile pub audio: InputFile, - /// Audio caption, 0-1024 characters after entities parsing - pub caption: String [into], } optional { + /// Audio caption, 0-1024 characters after entities parsing + pub caption: String [into], /// Mode for parsing entities in the audio caption. See [formatting options] for more details. /// /// [formatting options]: https://core.telegram.org/bots/api#formatting-options diff --git a/src/payloads/send_chat_action.rs b/src/payloads/send_chat_action.rs index d5a4a390..d4e4b859 100644 --- a/src/payloads/send_chat_action.rs +++ b/src/payloads/send_chat_action.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -20,13 +20,13 @@ impl_payload! { pub chat_id: ChatId [into], /// Type of action to broadcast. Choose one, depending on what the user is about to receive: typing for [text messages], upload_photo for [photos], record_video or upload_video for [videos], record_audio or upload_audio for [audio files], upload_document for [general files], find_location for [location data], record_video_note or upload_video_note for [video notes]. /// - /// [audio files]: crate::payloads::SendAudio /// [video notes]: crate::payloads::SendVideoNote - /// [videos]: crate::payloads::SendVideo - /// [text messages]: crate::payloads::SendMessage - /// [photos]: crate::payloads::SendPhoto + /// [audio files]: crate::payloads::SendAudio /// [general files]: crate::payloads::SendDocument /// [location data]: crate::payloads::SendLocation + /// [text messages]: crate::payloads::SendMessage + /// [photos]: crate::payloads::SendPhoto + /// [videos]: crate::payloads::SendVideo pub action: ChatAction, } } diff --git a/src/payloads/send_contact.rs b/src/payloads/send_contact.rs index 6980c662..2f617848 100644 --- a/src/payloads/send_contact.rs +++ b/src/payloads/send_contact.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_dice.rs b/src/payloads/send_dice.rs index 755b2b2f..a7400d23 100644 --- a/src/payloads/send_dice.rs +++ b/src/payloads/send_dice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -26,8 +26,8 @@ impl_payload! { pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_document.rs b/src/payloads/send_document.rs index 2d66f28e..8b9e9fbd 100644 --- a/src/payloads/send_document.rs +++ b/src/payloads/send_document.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -18,14 +18,14 @@ impl_payload! { /// /// [More info on Sending Files Âģ]: crate::types::InputFile pub document: InputFile, - /// Document caption (may also be used when resending documents by _file\_id_), 0-1024 characters after entities parsing - pub caption: String [into], } optional { /// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under . [More info on Sending Files Âģ] /// /// [More info on Sending Files Âģ]: crate::types::InputFile pub thumb: InputFile, + /// Document caption (may also be used when resending documents by _file\_id_), 0-1024 characters after entities parsing + pub caption: String [into], /// Mode for parsing entities in the audio caption. See [formatting options] for more details. /// /// [formatting options]: https://core.telegram.org/bots/api#formatting-options @@ -38,8 +38,8 @@ impl_payload! { pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_game.rs b/src/payloads/send_game.rs new file mode 100644 index 00000000..354573d9 --- /dev/null +++ b/src/payloads/send_game.rs @@ -0,0 +1,21 @@ +// This file is auto generated by `cg` (9a82143). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::Message; + +impl_payload! { + /// Use this method to send a game. On success, the sent [`Message`] is returned. + /// + /// [`Message`]: crate::types::Message + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SendGame (SendGameSetters) => Message { + required { + /// Unique identifier for the target chat + pub chat_id: u32, + /// Short name of the game, serves as the unique identifier for the game. Set up your games via Botfather. + pub game_short_name: String [into], + } + } +} diff --git a/src/payloads/send_invoice.rs b/src/payloads/send_invoice.rs index 292cb81a..8941bf16 100644 --- a/src/payloads/send_invoice.rs +++ b/src/payloads/send_invoice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_location.rs b/src/payloads/send_location.rs index fbaef731..f9a213e1 100644 --- a/src/payloads/send_location.rs +++ b/src/payloads/send_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -32,8 +32,8 @@ impl_payload! { pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_media_group.rs b/src/payloads/send_media_group.rs index 3799557d..3cdf6a1d 100644 --- a/src/payloads/send_media_group.rs +++ b/src/payloads/send_media_group.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_message.rs b/src/payloads/send_message.rs index a4f30955..9157d52f 100644 --- a/src/payloads/send_message.rs +++ b/src/payloads/send_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -32,8 +32,8 @@ impl_payload! { pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_photo.rs b/src/payloads/send_photo.rs index 10eeb698..4cc847bf 100644 --- a/src/payloads/send_photo.rs +++ b/src/payloads/send_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -18,10 +18,10 @@ impl_payload! { /// /// [More info on Sending Files Âģ]: crate::types::InputFile pub photo: InputFile, - /// Photo caption (may also be used when resending photos by _file\_id_), 0-1024 characters after entities parsing - pub caption: String [into], } optional { + /// Photo caption (may also be used when resending photos by _file\_id_), 0-1024 characters after entities parsing + pub caption: String [into], /// Mode for parsing entities in the photo caption. See [formatting options] for more details. /// /// [formatting options]: https://core.telegram.org/bots/api#formatting-options @@ -34,8 +34,8 @@ impl_payload! { pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_poll.rs b/src/payloads/send_poll.rs index 59f96801..5a83b5cc 100644 --- a/src/payloads/send_poll.rs +++ b/src/payloads/send_poll.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_sticker.rs b/src/payloads/send_sticker.rs index fddf0ddb..234b2951 100644 --- a/src/payloads/send_sticker.rs +++ b/src/payloads/send_sticker.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_venue.rs b/src/payloads/send_venue.rs index 355806d1..20ba4eb0 100644 --- a/src/payloads/send_venue.rs +++ b/src/payloads/send_venue.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_video.rs b/src/payloads/send_video.rs index c4b40121..e009fd0c 100644 --- a/src/payloads/send_video.rs +++ b/src/payloads/send_video.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -19,8 +19,6 @@ impl_payload! { /// /// [More info on Sending Files Âģ]: crate::types::InputFile pub video: InputFile, - /// Video caption (may also be used when resending videos by _file\_id_), 0-1024 characters after entities parsing - pub caption: String [into], } optional { /// Duration of the video in seconds @@ -33,6 +31,8 @@ impl_payload! { /// /// [More info on Sending Files Âģ]: crate::types::InputFile pub thumb: InputFile, + /// Video caption (may also be used when resending videos by _file\_id_), 0-1024 characters after entities parsing + pub caption: String [into], /// Mode for parsing entities in the video caption. See [formatting options] for more details. /// /// [formatting options]: https://core.telegram.org/bots/api#formatting-options diff --git a/src/payloads/send_video_note.rs b/src/payloads/send_video_note.rs index 47d5808a..8aecd4fb 100644 --- a/src/payloads/send_video_note.rs +++ b/src/payloads/send_video_note.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -8,8 +8,8 @@ use crate::types::{ChatId, InputFile, Message, ReplyMarkup}; impl_payload! { /// 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. On success, the sent [`Message`] is returned. /// - /// [v.4.0]: https://core.telegram.org/bots/api#document /// [`Message`]: crate::types::Message + /// [v.4.0]: https://core.telegram.org/bots/api#document #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] pub SendVideoNote (SendVideoNoteSetters) => Message { required { @@ -37,8 +37,8 @@ impl_payload! { pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_voice.rs b/src/payloads/send_voice.rs index bcea5f38..d30e5b36 100644 --- a/src/payloads/send_voice.rs +++ b/src/payloads/send_voice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -9,8 +9,8 @@ impl_payload! { /// Use this method to send audio files, if you want Telegram clients to display the file as a playable voice message. For this to work, your audio must be in an .OGG file encoded with OPUS (other formats may be sent as [`Audio`] or [`Document`]). On success, the sent [`Message`] is returned. Bots can currently send voice messages of up to 50 MB in size, this limit may be changed in the future. /// /// [`Audio`]: crate::types::Audio - /// [`Message`]: crate::types::Message /// [`Document`]: crate::types::Document + /// [`Message`]: crate::types::Message #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] pub SendVoice (SendVoiceSetters) => Message { required { @@ -20,10 +20,10 @@ impl_payload! { /// /// [More info on Sending Files Âģ]: crate::types::InputFile pub voice: InputFile, - /// Voice message caption, 0-1024 characters after entities parsing - pub caption: String [into], } optional { + /// Voice message caption, 0-1024 characters after entities parsing + pub caption: String [into], /// Mode for parsing entities in the voice message caption. See [formatting options] for more details. /// /// [formatting options]: https://core.telegram.org/bots/api#formatting-options diff --git a/src/payloads/set_chat_administrator_custom_title.rs b/src/payloads/set_chat_administrator_custom_title.rs index 414ca72f..31b63c5e 100644 --- a/src/payloads/set_chat_administrator_custom_title.rs +++ b/src/payloads/set_chat_administrator_custom_title.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_description.rs b/src/payloads/set_chat_description.rs index 35b88b3f..8abbe59a 100644 --- a/src/payloads/set_chat_description.rs +++ b/src/payloads/set_chat_description.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_permissions.rs b/src/payloads/set_chat_permissions.rs index 2c3a5cf9..196b320e 100644 --- a/src/payloads/set_chat_permissions.rs +++ b/src/payloads/set_chat_permissions.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_photo.rs b/src/payloads/set_chat_photo.rs index f72c682f..48b518cf 100644 --- a/src/payloads/set_chat_photo.rs +++ b/src/payloads/set_chat_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_sticker_set.rs b/src/payloads/set_chat_sticker_set.rs index 9c09328e..e28e569f 100644 --- a/src/payloads/set_chat_sticker_set.rs +++ b/src/payloads/set_chat_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_title.rs b/src/payloads/set_chat_title.rs index 76b72b8d..1ccc4b9b 100644 --- a/src/payloads/set_chat_title.rs +++ b/src/payloads/set_chat_title.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_game_score.rs b/src/payloads/set_game_score.rs new file mode 100644 index 00000000..8334f64c --- /dev/null +++ b/src/payloads/set_game_score.rs @@ -0,0 +1,33 @@ +// This file is auto generated by `cg` (9a82143). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::Message; + +impl_payload! { + /// Use this method to set the score of the specified user in a game. On success, returns the edited [`Message`]. Returns an error, if the new score is not greater than the user's current score in the chat and force is False. + /// + /// See also: [`SetGameScoreInline`](crate::payloads::SetGameScoreInline) + /// + /// [`Message`]: crate::types::Message + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SetGameScore (SetGameScoreSetters) => Message { + required { + /// User identifier + pub user_id: u32, + /// New score + pub score: u64, + /// Unique identifier for the target chat + pub chat_id: u32, + /// Identifier of the message to edit + pub message_id: i64, + } + optional { + /// Pass True, if the high score is allowed to decrease. This can be useful when fixing mistakes or banning cheaters + pub force: bool, + /// Pass True, if the game message should not be automatically edited to include the current scoreboard + pub disable_edit_message: bool, + } + } +} diff --git a/src/payloads/set_game_score_inline.rs b/src/payloads/set_game_score_inline.rs new file mode 100644 index 00000000..8b53accb --- /dev/null +++ b/src/payloads/set_game_score_inline.rs @@ -0,0 +1,29 @@ +// This file is auto generated by `cg` (9a82143). +// **DO NOT EDIT THIS FILE**, +// edit `cg` instead. +use serde::Serialize; + +use crate::types::Message; + +impl_payload! { + /// Use this method to set the score of the specified user in a game. On success, returns _True_. Returns an error, if the new score is not greater than the user's current score in the chat and force is False. + /// + /// See also: [`SetGameScore`](crate::payloads::SetGameScore) + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SetGameScoreInline (SetGameScoreInlineSetters) => Message { + required { + /// User identifier + pub user_id: u32, + /// New score + pub score: u64, + /// Identifier of the inline message + pub inline_message_id: String [into], + } + optional { + /// Pass True, if the high score is allowed to decrease. This can be useful when fixing mistakes or banning cheaters + pub force: bool, + /// Pass True, if the game message should not be automatically edited to include the current scoreboard + pub disable_edit_message: bool, + } + } +} diff --git a/src/payloads/set_my_commands.rs b/src/payloads/set_my_commands.rs index 4916fb6a..5cbc46cf 100644 --- a/src/payloads/set_my_commands.rs +++ b/src/payloads/set_my_commands.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_passport_data_errors.rs b/src/payloads/set_passport_data_errors.rs index 74ab729a..ebd3ad3a 100644 --- a/src/payloads/set_passport_data_errors.rs +++ b/src/payloads/set_passport_data_errors.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_sticker_position_in_set.rs b/src/payloads/set_sticker_position_in_set.rs index 80fad985..8b131b2a 100644 --- a/src/payloads/set_sticker_position_in_set.rs +++ b/src/payloads/set_sticker_position_in_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_sticker_set_thumb.rs b/src/payloads/set_sticker_set_thumb.rs index b9301535..7b142106 100644 --- a/src/payloads/set_sticker_set_thumb.rs +++ b/src/payloads/set_sticker_set_thumb.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_webhook.rs b/src/payloads/set_webhook.rs index b04b5bbc..c73b843f 100644 --- a/src/payloads/set_webhook.rs +++ b/src/payloads/set_webhook.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/setters.rs b/src/payloads/setters.rs index 98b55c80..2947bef2 100644 --- a/src/payloads/setters.rs +++ b/src/payloads/setters.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (878e847). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. @@ -15,18 +15,20 @@ pub use crate::payloads::{ EditMessageTextInlineSetters as _, EditMessageTextSetters as _, ExportChatInviteLinkSetters as _, ForwardMessageSetters as _, GetChatAdministratorsSetters as _, GetChatMemberSetters as _, GetChatMembersCountSetters as _, - GetChatSetters as _, GetFileSetters as _, GetMeSetters as _, GetMyCommandsSetters as _, - GetStickerSetSetters as _, GetUpdatesSetters as _, GetUserProfilePhotosSetters as _, - GetWebhookInfoSetters as _, KickChatMemberSetters as _, LeaveChatSetters as _, - PinChatMessageSetters as _, PromoteChatMemberSetters as _, RestrictChatMemberSetters as _, - SendAnimationSetters as _, SendAudioSetters as _, SendChatActionSetters as _, - SendContactSetters as _, SendDiceSetters as _, SendDocumentSetters as _, - SendInvoiceSetters as _, SendLocationSetters as _, SendMediaGroupSetters as _, - SendMessageSetters as _, SendPhotoSetters as _, SendPollSetters as _, SendStickerSetters as _, - SendVenueSetters as _, SendVideoNoteSetters as _, SendVideoSetters as _, SendVoiceSetters as _, + GetChatSetters as _, GetFileSetters as _, GetGameHighScoresSetters as _, GetMeSetters as _, + GetMyCommandsSetters as _, GetStickerSetSetters as _, GetUpdatesSetters as _, + GetUserProfilePhotosSetters as _, GetWebhookInfoSetters as _, KickChatMemberSetters as _, + LeaveChatSetters as _, PinChatMessageSetters as _, PromoteChatMemberSetters as _, + RestrictChatMemberSetters as _, SendAnimationSetters as _, SendAudioSetters as _, + SendChatActionSetters as _, SendContactSetters as _, SendDiceSetters as _, + SendDocumentSetters as _, SendGameSetters as _, SendInvoiceSetters as _, + SendLocationSetters as _, SendMediaGroupSetters as _, SendMessageSetters as _, + SendPhotoSetters as _, SendPollSetters as _, SendStickerSetters as _, SendVenueSetters as _, + SendVideoNoteSetters as _, SendVideoSetters as _, SendVoiceSetters as _, SetChatAdministratorCustomTitleSetters as _, SetChatDescriptionSetters as _, SetChatPermissionsSetters as _, SetChatPhotoSetters as _, SetChatStickerSetSetters as _, - SetChatTitleSetters as _, SetMyCommandsSetters as _, SetPassportDataErrorsSetters as _, + SetChatTitleSetters as _, SetGameScoreInlineSetters as _, SetGameScoreSetters as _, + SetMyCommandsSetters as _, SetPassportDataErrorsSetters as _, SetStickerPositionInSetSetters as _, SetStickerSetThumbSetters as _, SetWebhookSetters as _, StopMessageLiveLocationInlineSetters as _, StopMessageLiveLocationSetters as _, StopPollSetters as _, UnbanChatMemberSetters as _, UnpinChatMessageSetters as _, diff --git a/src/payloads/stop_message_live_location.rs b/src/payloads/stop_message_live_location.rs index 60c35d58..fdaa08e5 100644 --- a/src/payloads/stop_message_live_location.rs +++ b/src/payloads/stop_message_live_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -27,8 +27,8 @@ impl_payload! { optional { /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/stop_message_live_location_inline.rs b/src/payloads/stop_message_live_location_inline.rs index 9b0813be..b9e18e98 100644 --- a/src/payloads/stop_message_live_location_inline.rs +++ b/src/payloads/stop_message_live_location_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -24,8 +24,8 @@ impl_payload! { optional { /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards + /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/stop_poll.rs b/src/payloads/stop_poll.rs index b0abacbb..d1cf267f 100644 --- a/src/payloads/stop_poll.rs +++ b/src/payloads/stop_poll.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/unban_chat_member.rs b/src/payloads/unban_chat_member.rs index cd9e4fde..76d73e4e 100644 --- a/src/payloads/unban_chat_member.rs +++ b/src/payloads/unban_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/unpin_chat_message.rs b/src/payloads/unpin_chat_message.rs index dc78f594..bfc16094 100644 --- a/src/payloads/unpin_chat_message.rs +++ b/src/payloads/unpin_chat_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/upload_sticker_file.rs b/src/payloads/upload_sticker_file.rs index 908f50a3..82957d99 100644 --- a/src/payloads/upload_sticker_file.rs +++ b/src/payloads/upload_sticker_file.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (e634f65). +// This file is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/requests/requester.rs b/src/requests/requester.rs index 87a78331..c940eb80 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -1,10 +1,13 @@ +// We can't change Telegram API +#![allow(clippy::too_many_arguments)] + use crate::{ payloads::{GetMe, SendMessage, *}, requests::Request, types::{ AllowedUpdate, BotCommand, ChatAction, ChatId, ChatPermissions, DiceEmoji, InlineQueryResult, InputFile, InputMedia, InputSticker, LabeledPrice, PassportElementError, - PollType, + PollType, TargetMessage, }, }; @@ -17,7 +20,7 @@ use crate::{ pub trait Requester { type Err: std::error::Error + Send; - // This block is auto generated by `cg` (a8fa55a). + // This block is auto generated by `cg` (9a82143). // **DO NOT EDIT THIS BLOCK**, // edit `cg` instead. @@ -72,61 +75,45 @@ pub trait Requester { type SendPhoto: Request; - /// For Telegram documentation see [`SendPhoto`]. - fn send_photo(&self, chat_id: Ch, photo: InputFile, caption: Ca) -> Self::SendPhoto + /// For telegram documentation see [`SendPhoto`] + fn send_photo(&self, chat_id: C, photo: InputFile) -> Self::SendPhoto where - Ch: Into, - Ca: Into; + C: Into; type SendAudio: Request; - /// For Telegram documentation see [`SendAudio`]. - fn send_audio(&self, chat_id: Ch, audio: InputFile, caption: Ca) -> Self::SendAudio + /// For telegram documentation see [`SendAudio`] + fn send_audio(&self, chat_id: C, audio: InputFile) -> Self::SendAudio where - Ch: Into, - Ca: Into; + C: Into; type SendDocument: Request; - /// For Telegram documentation see [`SendDocument`]. - fn send_document( - &self, - chat_id: Ch, - document: InputFile, - caption: Ca, - ) -> Self::SendDocument + /// For telegram documentation see [`SendDocument`] + fn send_document(&self, chat_id: C, document: InputFile) -> Self::SendDocument where - Ch: Into, - Ca: Into; + C: Into; type SendVideo: Request; - /// For Telegram documentation see [`SendVideo`]. - fn send_video(&self, chat_id: Ch, video: InputFile, caption: Ca) -> Self::SendVideo + /// For telegram documentation see [`SendVideo`] + fn send_video(&self, chat_id: C, video: InputFile) -> Self::SendVideo where - Ch: Into, - Ca: Into; + C: Into; type SendAnimation: Request; - /// For Telegram documentation see [`SendAnimation`]. - fn send_animation( - &self, - chat_id: Ch, - animation: InputFile, - caption: Ca, - ) -> Self::SendAnimation + /// For telegram documentation see [`SendAnimation`] + fn send_animation(&self, chat_id: C, animation: InputFile) -> Self::SendAnimation where - Ch: Into, - Ca: Into; + C: Into; type SendVoice: Request; - /// For Telegram documentation see [`SendVoice`]. - fn send_voice(&self, chat_id: Ch, voice: InputFile, caption: Ca) -> Self::SendVoice + /// For telegram documentation see [`SendVoice`] + fn send_voice(&self, chat_id: C, voice: InputFile) -> Self::SendVoice where - Ch: Into, - Ca: Into; + C: Into; type SendVideoNote: Request; @@ -494,28 +481,20 @@ pub trait Requester { type EditMessageCaption: Request; - /// For Telegram documentation see [`EditMessageCaption`]. - fn edit_message_caption( - &self, - chat_id: Ch, - message_id: i32, - caption: Ca, - ) -> Self::EditMessageCaption + /// For telegram documentation see [`EditMessageCaption`] + fn edit_message_caption(&self, chat_id: C, message_id: i32) -> Self::EditMessageCaption where - Ch: Into, - Ca: Into; + C: Into; type EditMessageCaptionInline: Request; - /// For Telegram documentation see [`EditMessageCaptionInline`]. - fn edit_message_caption_inline( + /// For telegram documentation see [`EditMessageCaptionInline`] + fn edit_message_caption_inline( &self, inline_message_id: I, - caption: C, ) -> Self::EditMessageCaptionInline where - I: Into, - C: Into; + I: Into; type EditMessageMedia: Request; @@ -653,8 +632,7 @@ pub trait Requester { type SendInvoice: Request; - /// For Telegram documentation see [`SendInvoice`]. - #[allow(clippy::too_many_arguments)] + /// For telegram documentation see [`SendInvoice`] fn send_invoice( &self, chat_id: i32, @@ -699,4 +677,41 @@ pub trait Requester { fn set_passport_data_errors(&self, user_id: i32, errors: E) -> Self::SetPassportDataErrors where E: IntoIterator; + + type SendGame: Request; + + /// For telegram documentation see [`SendGame`] + fn send_game(&self, chat_id: u32, game_short_name: G) -> Self::SendGame + where + G: Into; + + type SetGameScore: Request; + + /// For telegram documentation see [`SetGameScore`] + fn set_game_score( + &self, + user_id: u32, + score: u64, + chat_id: u32, + message_id: i64, + ) -> Self::SetGameScore; + + type SetGameScoreInline: Request; + + /// For telegram documentation see [`SetGameScoreInline`] + fn set_game_score_inline( + &self, + user_id: u32, + score: u64, + inline_message_id: I, + ) -> Self::SetGameScoreInline + where + I: Into; + + type GetGameHighScores: Request; + + /// For telegram documentation see [`GetGameHighScores`] + fn get_game_high_scores(&self, user_id: u32, target: T) -> Self::GetGameHighScores + where + T: Into; } From 93bc2e6fee27709e93edd7807c99e6180baff3d5 Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 28 Dec 2020 14:46:49 +0300 Subject: [PATCH 174/755] Make requester methods doc nicer --- src/requests/requester.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/requests/requester.rs b/src/requests/requester.rs index c940eb80..09ff0641 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -20,7 +20,7 @@ use crate::{ pub trait Requester { type Err: std::error::Error + Send; - // This block is auto generated by `cg` (9a82143). + // This block is auto generated by `cg` (de3765b). // **DO NOT EDIT THIS BLOCK**, // edit `cg` instead. @@ -75,42 +75,42 @@ pub trait Requester { type SendPhoto: Request; - /// For telegram documentation see [`SendPhoto`] + /// For Telegram documentation see [`SendPhoto`]. fn send_photo(&self, chat_id: C, photo: InputFile) -> Self::SendPhoto where C: Into; type SendAudio: Request; - /// For telegram documentation see [`SendAudio`] + /// For Telegram documentation see [`SendAudio`]. fn send_audio(&self, chat_id: C, audio: InputFile) -> Self::SendAudio where C: Into; type SendDocument: Request; - /// For telegram documentation see [`SendDocument`] + /// For Telegram documentation see [`SendDocument`]. fn send_document(&self, chat_id: C, document: InputFile) -> Self::SendDocument where C: Into; type SendVideo: Request; - /// For telegram documentation see [`SendVideo`] + /// For Telegram documentation see [`SendVideo`]. fn send_video(&self, chat_id: C, video: InputFile) -> Self::SendVideo where C: Into; type SendAnimation: Request; - /// For telegram documentation see [`SendAnimation`] + /// For Telegram documentation see [`SendAnimation`]. fn send_animation(&self, chat_id: C, animation: InputFile) -> Self::SendAnimation where C: Into; type SendVoice: Request; - /// For telegram documentation see [`SendVoice`] + /// For Telegram documentation see [`SendVoice`]. fn send_voice(&self, chat_id: C, voice: InputFile) -> Self::SendVoice where C: Into; @@ -481,14 +481,14 @@ pub trait Requester { type EditMessageCaption: Request; - /// For telegram documentation see [`EditMessageCaption`] + /// For Telegram documentation see [`EditMessageCaption`]. fn edit_message_caption(&self, chat_id: C, message_id: i32) -> Self::EditMessageCaption where C: Into; type EditMessageCaptionInline: Request; - /// For telegram documentation see [`EditMessageCaptionInline`] + /// For Telegram documentation see [`EditMessageCaptionInline`]. fn edit_message_caption_inline( &self, inline_message_id: I, @@ -632,7 +632,7 @@ pub trait Requester { type SendInvoice: Request; - /// For telegram documentation see [`SendInvoice`] + /// For Telegram documentation see [`SendInvoice`]. fn send_invoice( &self, chat_id: i32, @@ -680,14 +680,14 @@ pub trait Requester { type SendGame: Request; - /// For telegram documentation see [`SendGame`] + /// For Telegram documentation see [`SendGame`]. fn send_game(&self, chat_id: u32, game_short_name: G) -> Self::SendGame where G: Into; type SetGameScore: Request; - /// For telegram documentation see [`SetGameScore`] + /// For Telegram documentation see [`SetGameScore`]. fn set_game_score( &self, user_id: u32, @@ -698,7 +698,7 @@ pub trait Requester { type SetGameScoreInline: Request; - /// For telegram documentation see [`SetGameScoreInline`] + /// For Telegram documentation see [`SetGameScoreInline`]. fn set_game_score_inline( &self, user_id: u32, @@ -710,7 +710,7 @@ pub trait Requester { type GetGameHighScores: Request; - /// For telegram documentation see [`GetGameHighScores`] + /// For Telegram documentation see [`GetGameHighScores`]. fn get_game_high_scores(&self, user_id: u32, target: T) -> Self::GetGameHighScores where T: Into; From 9052d4dfbbe21fa21dfecc994327e21dd5f201ff Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 28 Dec 2020 18:02:04 +0300 Subject: [PATCH 175/755] Remove already fixed FIXMEs --- src/adaptors.rs | 2 -- src/lib.rs | 1 - 2 files changed, 3 deletions(-) diff --git a/src/adaptors.rs b/src/adaptors.rs index 51db702c..1a308c57 100644 --- a/src/adaptors.rs +++ b/src/adaptors.rs @@ -31,5 +31,3 @@ pub use cache_me::CacheMe; pub use throttle::Throttle; pub use parse_mode::DefaultParseMode; - -// FIXME: move default `parse_mode` to adaptor diff --git a/src/lib.rs b/src/lib.rs index 7b33b931..67231953 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,6 @@ #[macro_use] mod local_macros; -// FIXME(waffle): rethink modules, find a place for wrappers. pub use self::{ bot::Bot, errors::{ApiError, DownloadError, RequestError}, From f4e3aa13eb98cacf805d4e91ea005d30599e192c Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 28 Dec 2020 18:45:45 +0300 Subject: [PATCH 176/755] Add `Bot::{set_,}api_url` methods The methods allow to set and get the Telegram bot API url. --- rustfmt.toml | 1 - src/bot/api_url.rs | 8 ++++---- src/bot/mod.rs | 37 +++++++++++++++++++++++++++++++++++-- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index c61c5a92..60b85bac 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -2,5 +2,4 @@ format_code_in_doc_comments = true wrap_comments = true format_strings = true merge_imports = true -use_small_heuristics = "Max" use_field_init_shorthand = true diff --git a/src/bot/api_url.rs b/src/bot/api_url.rs index ed10afc8..9db6a826 100644 --- a/src/bot/api_url.rs +++ b/src/bot/api_url.rs @@ -1,9 +1,9 @@ +use std::sync::Arc; + #[derive(Debug, Clone)] pub(crate) enum ApiUrl { Default, - // FIXME: remove #[allow] when we use this variant - #[allow(dead_code)] - Custom(reqwest::Url), + Custom(Arc), } impl ApiUrl { @@ -12,7 +12,7 @@ impl ApiUrl { // FIXME(waffle): parse once ApiUrl::Default => reqwest::Url::parse(crate::net::TELEGRAM_API_URL) .expect("failed to parse default url"), - ApiUrl::Custom(url) => url.clone(), + ApiUrl::Custom(url) => (&**url).clone(), } } } diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 8fb01095..5a7654a3 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -100,15 +100,48 @@ impl Bot { Self::with_client(&get_env(TELOXIDE_TOKEN), client) } - /// Returns currently used token + /// Sets custom api url. + /// + /// For example you can run your own [Telegram bot API server][tbas] and set + /// it's url using this method. + /// + /// [tbas]: https://github.com/tdlib/telegram-bot-api + /// + /// ## Examples + /// + /// ``` + /// use teloxide_core::{ + /// requests::{Request, Requester}, + /// Bot, + /// }; + /// + /// # async { + /// // Setup bot + /// let url = reqwest::Url::parse("https://localhost/tbas").unwrap(); + /// let bot = Bot::new("TOKEN").set_api_url(url); + /// // All methods will use "https://localhost/tbas" as an api url + /// bot.get_me().send().await + /// # }; + /// ``` + pub fn set_api_url(mut self, url: reqwest::Url) -> Self { + self.api_url = ApiUrl::Custom(Arc::new(url)); + self + } + + /// Returns currently used token. pub fn token(&self) -> &str { &self.token } - /// Returns currently used http-client + /// Returns currently used http-client. pub fn client(&self) -> &Client { &self.client } + + /// Returns currently used token API url. + pub fn api_url(&self) -> reqwest::Url { + self.api_url.get() + } } impl Bot { From 50d3d67f931d55230ca848bcbf68dd2afddedb01 Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 28 Dec 2020 19:03:54 +0300 Subject: [PATCH 177/755] fmt --- src/adaptors/cache_me.rs | 5 +- src/adaptors/parse_mode.rs | 5 +- src/adaptors/throttle.rs | 65 ++++++-- src/bot/api.rs | 40 ++++- src/bot/download.rs | 9 +- src/errors.rs | 35 +++-- src/net/download.rs | 27 ++-- src/net/mod.rs | 16 +- src/net/telegram_response.rs | 12 +- src/requests/request.rs | 13 +- src/serde_multipart/serializers.rs | 42 +++-- src/serde_multipart/unserializers.rs | 32 +++- .../unserializers/input_file.rs | 15 +- src/types/bot_command.rs | 5 +- src/types/chat.rs | 40 ++++- src/types/chat_member.rs | 66 +++++--- src/types/encrypted_credentials.rs | 6 +- src/types/encrypted_passport_element.rs | 68 ++++++-- src/types/game.rs | 2 - src/types/game_high_score.rs | 6 +- src/types/inline_keyboard_button.rs | 15 +- src/types/inline_keyboard_markup.rs | 16 +- src/types/inline_query.rs | 8 +- src/types/inline_query_result_game.rs | 6 +- src/types/input_file.rs | 16 +- src/types/input_media.rs | 13 +- src/types/input_message_content.rs | 12 +- src/types/keyboard_button.rs | 76 ++++++--- src/types/keyboard_button_poll_type.rs | 4 +- src/types/label_price.rs | 10 +- src/types/location.rs | 5 +- src/types/mask_position.rs | 7 +- src/types/me.rs | 7 +- src/types/message.rs | 146 ++++++++++++++---- src/types/message_entity.rs | 18 ++- .../non_telegram_types/non_strict_vec.rs | 6 +- src/types/passport_data.rs | 5 +- src/types/passport_element_error.rs | 51 ++++-- src/types/poll.rs | 5 +- src/types/poll_answer.rs | 6 +- src/types/shipping_option.rs | 11 +- src/types/shipping_query.rs | 7 +- src/types/update.rs | 4 +- src/types/user_profile_photos.rs | 5 +- 44 files changed, 740 insertions(+), 228 deletions(-) diff --git a/src/adaptors/cache_me.rs b/src/adaptors/cache_me.rs index 2a23a2ae..539004bd 100644 --- a/src/adaptors/cache_me.rs +++ b/src/adaptors/cache_me.rs @@ -30,7 +30,10 @@ impl CacheMe { /// /// [`RequesterExt::cache_me`]: crate::requests::RequesterExt::cache_me pub fn new(bot: B) -> CacheMe { - Self { bot, me: Arc::new(OnceCell::new()) } + Self { + bot, + me: Arc::new(OnceCell::new()), + } } /// Allows to access inner bot diff --git a/src/adaptors/parse_mode.rs b/src/adaptors/parse_mode.rs index df7a612b..f5802d2d 100644 --- a/src/adaptors/parse_mode.rs +++ b/src/adaptors/parse_mode.rs @@ -18,7 +18,10 @@ impl DefaultParseMode { /// /// [`RequesterExt::parse_mode`]: crate::requests::RequesterExt::parse_mode pub fn new(bot: B, parse_mode: ParseMode) -> Self { - Self { bot, mode: parse_mode } + Self { + bot, + mode: parse_mode, + } } /// Allows to access the inner bot. diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index db26038f..a71fafa5 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -99,7 +99,11 @@ pub struct Limits { /// [tgdoc]: https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this impl Default for Limits { fn default() -> Self { - Self { chat_s: 1, overall_s: 30, chat_m: 20 } + Self { + chat_s: 1, + overall_s: 30, + chat_m: 20, + } } } @@ -244,7 +248,10 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender) // as truncates which is ok since in case of truncation it would always be >= // limits.overall_s - let used = history.iter().take_while(|(_, time)| time > &sec_back).count() as u32; + let used = history + .iter() + .take_while(|(_, time)| time > &sec_back) + .count() as u32; let mut allowed = limits.overall_s.saturating_sub(used); if allowed == 0 { @@ -307,7 +314,10 @@ impl Throttle { let (queue_tx, queue_rx) = mpsc::channel(buffer as usize); let worker = worker(limits, queue_rx); - let this = Self { bot, queue: queue_tx }; + let this = Self { + bot, + queue: queue_tx, + }; (this, worker) } @@ -526,7 +536,11 @@ where let (tx, rx) = channel(); let id = self.2(self.payload_ref()); let send = self.1.send_t((id, tx)); - ThrottlingSend(ThrottlingSendInner::Registering { request: self.0, send, wait: rx }) + ThrottlingSend(ThrottlingSendInner::Registering { + request: self.0, + send, + wait: rx, + }) } fn send_ref(&self) -> Self::SendRef { @@ -541,7 +555,11 @@ where // should **not** do any kind of work, so it's ok. let request = self.0.send_ref(); - ThrottlingSendRef(ThrottlingSendRefInner::Registering { request, send, wait: rx }) + ThrottlingSendRef(ThrottlingSendRefInner::Registering { + request, + send, + wait: rx, + }) } } @@ -575,11 +593,18 @@ impl Future for ThrottlingSend { let mut this = self.as_mut().project().0; match this.as_mut().project() { - SendProj::Registering { request: _, send, wait: _ } => match send.poll(cx) { + SendProj::Registering { + request: _, + send, + wait: _, + } => match send.poll(cx) { Poll::Pending => Poll::Pending, Poll::Ready(res) => { - if let SendRepl::Registering { request, send: _, wait } = - this.as_mut().project_replace(ThrottlingSendInner::Done) + if let SendRepl::Registering { + request, + send: _, + wait, + } = this.as_mut().project_replace(ThrottlingSendInner::Done) { match res { Ok(()) => this @@ -588,9 +613,9 @@ impl Future for ThrottlingSend { // The worker is unlikely to drop queue before sending all requests, // but just in case it has dropped the queue, we want to just send the // request. - Err(_) => this - .as_mut() - .project_replace(ThrottlingSendInner::Sent { fut: request.send() }), + Err(_) => this.as_mut().project_replace(ThrottlingSendInner::Sent { + fut: request.send(), + }), }; } @@ -607,8 +632,9 @@ impl Future for ThrottlingSend { if let SendRepl::Pending { request, wait: _ } = this.as_mut().project_replace(ThrottlingSendInner::Done) { - this.as_mut() - .project_replace(ThrottlingSendInner::Sent { fut: request.send() }); + this.as_mut().project_replace(ThrottlingSendInner::Sent { + fut: request.send(), + }); } self.poll(cx) @@ -654,11 +680,18 @@ impl Future for ThrottlingSendRef { let mut this = self.as_mut().project().0; match this.as_mut().project() { - SendRefProj::Registering { request: _, send, wait: _ } => match send.poll(cx) { + SendRefProj::Registering { + request: _, + send, + wait: _, + } => match send.poll(cx) { Poll::Pending => Poll::Pending, Poll::Ready(res) => { - if let SendRefRepl::Registering { request, send: _, wait } = - this.as_mut().project_replace(ThrottlingSendRefInner::Done) + if let SendRefRepl::Registering { + request, + send: _, + wait, + } = this.as_mut().project_replace(ThrottlingSendRefInner::Done) { match res { Ok(()) => this diff --git a/src/bot/api.rs b/src/bot/api.rs index 01463afa..f03e5cd9 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -25,7 +25,10 @@ impl Requester for Bot { U: Into, A: IntoIterator, { - Self::SetWebhook::new(self.clone(), payloads::SetWebhook::new(url, allowed_updates)) + Self::SetWebhook::new( + self.clone(), + payloads::SetWebhook::new(url, allowed_updates), + ) } type DeleteWebhook = JsonRequest; @@ -116,7 +119,10 @@ impl Requester for Bot { where C: Into, { - Self::SendAnimation::new(self.clone(), payloads::SendAnimation::new(chat_id, animation)) + Self::SendAnimation::new( + self.clone(), + payloads::SendAnimation::new(chat_id, animation), + ) } type SendVoice = MultipartRequest; @@ -134,7 +140,10 @@ impl Requester for Bot { where C: Into, { - Self::SendVideoNote::new(self.clone(), payloads::SendVideoNote::new(chat_id, video_note)) + Self::SendVideoNote::new( + self.clone(), + payloads::SendVideoNote::new(chat_id, video_note), + ) } type SendMediaGroup = MultipartRequest; @@ -331,7 +340,10 @@ impl Requester for Bot { where C: Into, { - Self::KickChatMember::new(self.clone(), payloads::KickChatMember::new(chat_id, user_id)) + Self::KickChatMember::new( + self.clone(), + payloads::KickChatMember::new(chat_id, user_id), + ) } type UnbanChatMember = JsonRequest; @@ -340,7 +352,10 @@ impl Requester for Bot { where C: Into, { - Self::UnbanChatMember::new(self.clone(), payloads::UnbanChatMember::new(chat_id, user_id)) + Self::UnbanChatMember::new( + self.clone(), + payloads::UnbanChatMember::new(chat_id, user_id), + ) } type RestrictChatMember = JsonRequest; @@ -458,7 +473,10 @@ impl Requester for Bot { where C: Into, { - Self::PinChatMessage::new(self.clone(), payloads::PinChatMessage::new(chat_id, message_id)) + Self::PinChatMessage::new( + self.clone(), + payloads::PinChatMessage::new(chat_id, message_id), + ) } type UnpinChatMessage = JsonRequest; @@ -713,7 +731,10 @@ impl Requester for Bot { where C: Into, { - Self::DeleteMessage::new(self.clone(), payloads::DeleteMessage::new(chat_id, message_id)) + Self::DeleteMessage::new( + self.clone(), + payloads::DeleteMessage::new(chat_id, message_id), + ) } type SendSticker = MultipartRequest; @@ -903,7 +924,10 @@ impl Requester for Bot { where G: Into, { - Self::SendGame::new(self.clone(), payloads::SendGame::new(chat_id, game_short_name)) + Self::SendGame::new( + self.clone(), + payloads::SendGame::new(chat_id, game_short_name), + ) } type SetGameScore = JsonRequest; diff --git a/src/bot/download.rs b/src/bot/download.rs index dad76b31..32b19d6e 100644 --- a/src/bot/download.rs +++ b/src/bot/download.rs @@ -20,7 +20,14 @@ impl<'w> Download<'w> for Bot { path: &str, destination: &'w mut (dyn AsyncWrite + Unpin + Send), ) -> Self::Fut { - net::download_file(&self.client, self.api_url.get(), &self.token, path, destination).boxed() + net::download_file( + &self.client, + self.api_url.get(), + &self.token, + path, + destination, + ) + .boxed() } type StreamErr = reqwest::Error; diff --git a/src/errors.rs b/src/errors.rs index 9c65dd6d..3476ceed 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -72,9 +72,10 @@ pub enum ApiError { /// 1. [`EditMessageText`] /// /// [`EditMessageText`]: crate::payloads::EditMessageText - #[serde(rename = "Bad Request: message is not modified: specified new message content and \ - reply markup are exactly the same as a current content and reply markup \ - of the message")] + #[serde( + rename = "Bad Request: message is not modified: specified new message content and reply \ + markup are exactly the same as a current content and reply markup of the message" + )] MessageNotModified, /// Occurs when bot tries to forward or delete a message which was deleted. @@ -294,8 +295,10 @@ pub enum ApiError { /// 1. [`AnswerCallbackQuery`] /// /// [`AnswerCallbackQuery`]: crate::payloads::AnswerCallbackQuery - #[serde(rename = "Bad Request: query is too old and response timeout expired or query id is \ - invalid")] + #[serde( + rename = "Bad Request: query is too old and response timeout expired or query id is \ + invalid" + )] InvalidQueryID, /// Occurs when bot tries to send InlineKeyboardMarkup with invalid button @@ -323,8 +326,10 @@ pub enum ApiError { /// 1. [`SendMessage`] /// /// [`SendMessage`]: crate::payloads::SendMessage - #[serde(rename = "Bad Request: can't parse inline keyboard button: Text buttons are \ - unallowed in the inline keyboard")] + #[serde( + rename = "Bad Request: can't parse inline keyboard button: Text buttons are unallowed in \ + the inline keyboard" + )] TextButtonsAreUnallowed, /// Occurs when bot tries to get file by wrong file id. @@ -417,8 +422,10 @@ pub enum ApiError { /// 1. [`SetWebhook`] /// /// [`SetWebhook`]: crate::payloads::SetWebhook - #[serde(rename = "Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 \ - or 8443")] + #[serde( + rename = "Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 or \ + 8443" + )] BadWebhookPort, /// Occurs when bot tries to set webhook to unknown host. @@ -427,7 +434,9 @@ pub enum ApiError { /// 1. [`SetWebhook`] /// /// [`SetWebhook`]: crate::payloads::SetWebhook - #[serde(rename = "Bad Request: bad webhook: Failed to resolve host: Name or service not known")] + #[serde( + rename = "Bad Request: bad webhook: Failed to resolve host: Name or service not known" + )] UnknownHost, /// Occurs when bot tries to set webhook to invalid URL. @@ -509,8 +518,10 @@ pub enum ApiError { /// 1. [`GetUpdates`] /// /// [`GetUpdates`]: crate::payloads::GetUpdates - #[serde(rename = "Conflict: terminated by other getUpdates request; make sure that only one \ - bot instance is running")] + #[serde( + rename = "Conflict: terminated by other getUpdates request; make sure that only one bot \ + instance is running" + )] TerminatedByOtherGetUpdates, /// Occurs when bot tries to get file by invalid file id. diff --git a/src/net/download.rs b/src/net/download.rs index 5c123cb7..da05352d 100644 --- a/src/net/download.rs +++ b/src/net/download.rs @@ -94,15 +94,18 @@ pub fn download_file<'o, D>( where D: ?Sized + AsyncWrite + Unpin, { - client.get(file_url(api_url, token, path)).send().then(move |r| async move { - let mut res = r?.error_for_status()?; + client + .get(file_url(api_url, token, path)) + .send() + .then(move |r| async move { + let mut res = r?.error_for_status()?; - while let Some(chunk) = res.chunk().await? { - dst.write_all(&chunk).await?; - } + while let Some(chunk) = res.chunk().await? { + dst.write_all(&chunk).await?; + } - Ok(()) - }) + Ok(()) + }) } /// Download a file from Telegram as [`Stream`]. @@ -116,8 +119,11 @@ pub fn download_file_stream( token: &str, path: &str, ) -> impl Stream> + 'static { - client.get(file_url(api_url, token, path)).send().into_stream().flat_map(|res| { - match res.and_then(Response::error_for_status) { + client + .get(file_url(api_url, token, path)) + .send() + .into_stream() + .flat_map(|res| match res.and_then(Response::error_for_status) { Ok(res) => Either::Left(unfold(res, |mut res| async { match res.chunk().await { Err(err) => Some((Err(err), res)), @@ -126,6 +132,5 @@ pub fn download_file_stream( } })), Err(err) => Either::Right(once(ready(Err(err)))), - } - }) + }) } diff --git a/src/net/mod.rs b/src/net/mod.rs index 119213aa..944e1538 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -18,16 +18,24 @@ pub const TELEGRAM_API_URL: &str = "https://api.telegram.org"; /// /// [Telegram documentation]: https://core.telegram.org/bots/api#making-requests fn method_url(base: reqwest::Url, token: &str, method_name: &str) -> reqwest::Url { - base.join(&format!("/bot{token}/{method}", token = token, method = method_name)) - .expect("failed to format url") + base.join(&format!( + "/bot{token}/{method}", + token = token, + method = method_name + )) + .expect("failed to format url") } /// Creates URL for downloading a file. See the [Telegram documentation]. /// /// [Telegram documentation]: https://core.telegram.org/bots/api#file fn file_url(base: reqwest::Url, token: &str, file_path: &str) -> reqwest::Url { - base.join(&format!("file/bot{token}/{file}", token = token, file = file_path)) - .expect("failed to format url") + base.join(&format!( + "file/bot{token}/{file}", + token = token, + file = file_path + )) + .expect("failed to format url") } #[cfg(test)] diff --git a/src/net/telegram_response.rs b/src/net/telegram_response.rs index 04f8f4c1..34242bb3 100644 --- a/src/net/telegram_response.rs +++ b/src/net/telegram_response.rs @@ -33,7 +33,12 @@ impl Into> for TelegramResponse { fn into(self) -> Result { match self { TelegramResponse::Ok { response, .. } => Ok(response), - TelegramResponse::Err { error, error_code, response_parameters, .. } => { + TelegramResponse::Err { + error, + error_code, + response_parameters, + .. + } => { if let Some(params) = response_parameters { match params { ResponseParameters::RetryAfter(i) => Err(RequestError::RetryAfter(i)), @@ -64,7 +69,10 @@ mod tests { assert!(matches!( val, - TelegramResponse::Err { error: ApiError::TerminatedByOtherGetUpdates, .. } + TelegramResponse::Err { + error: ApiError::TerminatedByOtherGetUpdates, + .. + } )); } diff --git a/src/requests/request.rs b/src/requests/request.rs index abc48fa1..cdde179b 100644 --- a/src/requests/request.rs +++ b/src/requests/request.rs @@ -39,10 +39,14 @@ pub trait Request: HasPayload { /// Send this request. /// /// ## Examples - // FIXME(waffle): ignored until full request redesign lands - /// ```ignore + /// ``` /// # async { - /// use teloxide_core::{methods::GetMe, requests::{Request, RequestJson}, types::User, bot::Bot}; + /// use teloxide_core::{ + /// bot::Bot, + /// methods::GetMe, + /// requests::{Request, RequestJson}, + /// types::User, + /// }; /// /// let bot = Bot::new("TOKEN"); /// let method = GetMe::new(); @@ -63,8 +67,7 @@ pub trait Request: HasPayload { /// and then serializing it, this method should just serialize the data.) /// /// ## Examples - // FIXME(waffle): ignored until full request redesign lands - /// ```ignore + /// ``` /// # async { /// use teloxide_core::prelude::*; /// diff --git a/src/serde_multipart/serializers.rs b/src/serde_multipart/serializers.rs index e31da1a4..5bdce22b 100644 --- a/src/serde_multipart/serializers.rs +++ b/src/serde_multipart/serializers.rs @@ -216,7 +216,11 @@ impl Serializer for MultipartTopLvlSerializer { } fn serialize_map(self, _: Option) -> Result { - Ok(MultipartMapSerializer { parts: vec![], files: vec![], key: None }) + Ok(MultipartMapSerializer { + parts: vec![], + files: vec![], + key: None, + }) } fn serialize_struct( @@ -245,7 +249,10 @@ pub(crate) struct MultipartSerializer { impl MultipartSerializer { fn new() -> Self { - Self { parts: Vec::new(), files: vec![] } + Self { + parts: Vec::new(), + files: vec![], + } } } @@ -269,8 +276,10 @@ impl SerializeStruct for MultipartSerializer { } fn end(self) -> Result { - let form = - self.parts.into_iter().fold(Form::new(), |acc, (key, value)| acc.part(key, value)); + let form = self + .parts + .into_iter() + .fold(Form::new(), |acc, (key, value)| acc.part(key, value)); if self.files.is_empty() { //Ok(Either::Left(ready(Ok(form)))) @@ -322,8 +331,10 @@ impl SerializeMap for MultipartMapSerializer { } fn end(self) -> Result { - let form = - self.parts.into_iter().fold(Form::new(), |acc, (key, value)| acc.part(key, value)); + let form = self + .parts + .into_iter() + .fold(Form::new(), |acc, (key, value)| acc.part(key, value)); if self.files.is_empty() { //Ok(Either::Left(ready(Ok(form)))) @@ -479,7 +490,10 @@ impl Serializer for PartSerializer { } fn serialize_seq(self, _: Option) -> Result { - Ok(Self::SerializeSeq { array_json_parts: vec![], files: vec![] }) + Ok(Self::SerializeSeq { + array_json_parts: vec![], + files: vec![], + }) } fn serialize_tuple(self, _: usize) -> Result { @@ -556,7 +570,9 @@ impl SerializeStructVariant for PartFromFile { where T: Serialize, { - self.inner.serialize_field(key, value).map_err(Error::InputFileUnserializer) + self.inner + .serialize_field(key, value) + .map_err(Error::InputFileUnserializer) } fn end(self) -> Result { @@ -638,7 +654,10 @@ impl SerializeStruct for PartSerializerStruct { serde_json::ser::State::First => serde_json::ser::State::First, serde_json::ser::State::Rest => serde_json::ser::State::Rest, }; - let mut ser = serde_json::ser::Compound::Map { ser: &mut self.0, state }; + let mut ser = serde_json::ser::Compound::Map { + ser: &mut self.0, + state, + }; // special case media (required for `edit_message_media` to work) if key == "media" { @@ -673,7 +692,10 @@ impl SerializeStruct for PartSerializerStruct { serde_json::ser::State::First => serde_json::ser::State::First, serde_json::ser::State::Rest => serde_json::ser::State::Rest, }; - let ser = serde_json::ser::Compound::Map { ser: &mut self.0, state }; + let ser = serde_json::ser::Compound::Map { + ser: &mut self.0, + state, + }; SerializeStruct::end(ser)?; let json = self.0.into_inner(); diff --git a/src/serde_multipart/unserializers.rs b/src/serde_multipart/unserializers.rs index b4b4775f..1068f211 100644 --- a/src/serde_multipart/unserializers.rs +++ b/src/serde_multipart/unserializers.rs @@ -12,10 +12,22 @@ use serde::ser; #[derive(Debug, PartialEq, Eq)] pub enum UnserializerError { Custom(String), - UnsupportedType { ty: &'static str, supported: &'static str }, - UnexpectedField { name: &'static str, expected: &'static [&'static str] }, - UnexpectedVariant { name: &'static str, expected: &'static [&'static str] }, - WrongLen { len: usize, expected: usize }, + UnsupportedType { + ty: &'static str, + supported: &'static str, + }, + UnexpectedField { + name: &'static str, + expected: &'static [&'static str], + }, + UnexpectedVariant { + name: &'static str, + expected: &'static [&'static str], + }, + WrongLen { + len: usize, + expected: usize, + }, } impl ser::Error for UnserializerError { @@ -38,7 +50,11 @@ impl Display for UnserializerError { ), Self::Custom(s) => write!(f, "Custom serde error: {}", s), Self::UnsupportedType { ty, supported } => { - write!(f, "Unsupported type: `{}`, supported type(s): `{}`", ty, supported) + write!( + f, + "Unsupported type: `{}`, supported type(s): `{}`", + ty, supported + ) } Self::UnexpectedVariant { name, expected } => write!( f, @@ -74,8 +90,10 @@ fn test() { let value = InputFile::FileId(String::from("file_id")); assert_eq!(value.serialize(InputFileUnserializer::NotMem), Ok(value)); - let value = - InputFile::Memory { file_name: String::from("name"), data: Cow::Owned(vec![1, 2, 3]) }; + let value = InputFile::Memory { + file_name: String::from("name"), + data: Cow::Owned(vec![1, 2, 3]), + }; assert_eq!(value.serialize(InputFileUnserializer::memory()), Ok(value)); let value = InputFile::File("a/b/c".into()); diff --git a/src/serde_multipart/unserializers/input_file.rs b/src/serde_multipart/unserializers/input_file.rs index d3cc94ff..bcdd11ff 100644 --- a/src/serde_multipart/unserializers/input_file.rs +++ b/src/serde_multipart/unserializers/input_file.rs @@ -13,13 +13,19 @@ use crate::{ }; pub(crate) enum InputFileUnserializer { - Memory { file_name: String, data: Cow<'static, [u8]> }, + Memory { + file_name: String, + data: Cow<'static, [u8]>, + }, NotMem, } impl InputFileUnserializer { pub(crate) fn memory() -> Self { - Self::Memory { file_name: String::new(), data: Cow::Borrowed(&[]) } + Self::Memory { + file_name: String::new(), + data: Cow::Borrowed(&[]), + } } } @@ -73,7 +79,10 @@ impl Serializer for InputFileUnserializer { len: usize, ) -> Result { if name != "InputFile" { - return Err(UnserializerError::UnsupportedType { ty: name, supported: "InputFile" }); + return Err(UnserializerError::UnsupportedType { + ty: name, + supported: "InputFile", + }); } if variant != "Memory" { diff --git a/src/types/bot_command.rs b/src/types/bot_command.rs index cb6d9c6e..5ea4f1c2 100644 --- a/src/types/bot_command.rs +++ b/src/types/bot_command.rs @@ -20,7 +20,10 @@ impl BotCommand { S1: Into, S2: Into, { - Self { command: command.into(), description: description.into() } + Self { + command: command.into(), + description: description.into(), + } } pub fn command(mut self, val: S) -> Self diff --git a/src/types/chat.rs b/src/types/chat.rs index 3a9e7dfe..3ac14786 100644 --- a/src/types/chat.rs +++ b/src/types/chat.rs @@ -26,7 +26,11 @@ pub struct Chat { impl Chat { pub fn new(id: i64, kind: ChatKind) -> Self { - Self { id, kind, photo: None } + Self { + id, + kind, + photo: None, + } } pub fn id(mut self, val: i64) -> Self { @@ -89,7 +93,13 @@ pub struct ChatPublic { impl ChatPublic { pub fn new(kind: PublicChatKind) -> Self { - Self { title: None, kind, description: None, invite_link: None, pinned_message: None } + Self { + title: None, + kind, + description: None, + invite_link: None, + pinned_message: None, + } } pub fn title(mut self, val: S) -> Self @@ -261,7 +271,10 @@ impl<'de> serde::de::Visitor<'de> for PrivateChatKindVisitor { fn visit_str(self, v: &str) -> Result { match v { "private" => Ok(()), - _ => Err(E::invalid_value(serde::de::Unexpected::Str(v), &r#""private""#)), + _ => Err(E::invalid_value( + serde::de::Unexpected::Str(v), + &r#""private""#, + )), } } } @@ -278,16 +291,31 @@ impl Chat { matches!(self.kind, ChatKind::Private(_)) } pub fn is_group(&self) -> bool { - matches!(self.kind, ChatKind::Public(ChatPublic { kind: PublicChatKind::Group(_), .. })) + matches!( + self.kind, + ChatKind::Public(ChatPublic { + kind: PublicChatKind::Group(_), + .. + }) + ) } pub fn is_supergroup(&self) -> bool { matches!( self.kind, - ChatKind::Public(ChatPublic { kind: PublicChatKind::Supergroup(_), .. }) + ChatKind::Public(ChatPublic { + kind: PublicChatKind::Supergroup(_), + .. + }) ) } pub fn is_channel(&self) -> bool { - matches!(self.kind, ChatKind::Public(ChatPublic { kind: PublicChatKind::Channel(_), .. })) + matches!( + self.kind, + ChatKind::Public(ChatPublic { + kind: PublicChatKind::Channel(_), + .. + }) + ) } pub fn is_chat(&self) -> bool { diff --git a/src/types/chat_member.rs b/src/types/chat_member.rs index 46c5c7cf..9aa5101c 100644 --- a/src/types/chat_member.rs +++ b/src/types/chat_member.rs @@ -163,7 +163,9 @@ impl ChatMemberKind { /// Getter for [`Administrator::can_change_info`] field. pub fn can_change_info(&self) -> Option { match &self { - Self::Administrator(Administrator { can_change_info, .. }) => Some(*can_change_info), + Self::Administrator(Administrator { + can_change_info, .. + }) => Some(*can_change_info), Self::Creator(_) | Self::Member | Self::Restricted(_) @@ -175,7 +177,9 @@ impl ChatMemberKind { /// Getter for [`Administrator::can_post_messages`] field. pub fn can_post_messages(&self) -> Option { match &self { - Self::Administrator(Administrator { can_post_messages, .. }) => *can_post_messages, + Self::Administrator(Administrator { + can_post_messages, .. + }) => *can_post_messages, Self::Creator(_) | Self::Member | Self::Restricted(_) @@ -187,7 +191,9 @@ impl ChatMemberKind { /// Getter for [`Administrator::can_edit_messages`] field. pub fn can_edit_messages(&self) -> Option { match &self { - Self::Administrator(Administrator { can_edit_messages, .. }) => *can_edit_messages, + Self::Administrator(Administrator { + can_edit_messages, .. + }) => *can_edit_messages, Self::Creator(_) | Self::Member | Self::Restricted(_) @@ -199,9 +205,10 @@ impl ChatMemberKind { /// Getter for [`Administrator::can_delete_messages`] field. pub fn can_delete_messages(&self) -> Option { match &self { - Self::Administrator(Administrator { can_delete_messages, .. }) => { - Some(*can_delete_messages) - } + Self::Administrator(Administrator { + can_delete_messages, + .. + }) => Some(*can_delete_messages), Self::Creator(_) | Self::Member | Self::Restricted(_) @@ -213,7 +220,9 @@ impl ChatMemberKind { /// Getter for [`Administrator::can_invite_users`] field. pub fn can_invite_users(&self) -> Option { match &self { - Self::Administrator(Administrator { can_invite_users, .. }) => Some(*can_invite_users), + Self::Administrator(Administrator { + can_invite_users, .. + }) => Some(*can_invite_users), Self::Creator(_) | Self::Member | Self::Restricted(_) @@ -225,9 +234,10 @@ impl ChatMemberKind { /// Getter for [`Administrator::can_restrict_members`] field. pub fn can_restrict_members(&self) -> Option { match &self { - Self::Administrator(Administrator { can_restrict_members, .. }) => { - Some(*can_restrict_members) - } + Self::Administrator(Administrator { + can_restrict_members, + .. + }) => Some(*can_restrict_members), Self::Creator(_) | Self::Member | Self::Restricted(_) @@ -239,7 +249,9 @@ impl ChatMemberKind { /// Getter for [`Administrator::can_pin_messages`] field. pub fn can_pin_messages(&self) -> Option { match &self { - Self::Administrator(Administrator { can_pin_messages, .. }) => *can_pin_messages, + Self::Administrator(Administrator { + can_pin_messages, .. + }) => *can_pin_messages, Self::Creator(_) | Self::Member | Self::Restricted(_) @@ -251,9 +263,10 @@ impl ChatMemberKind { /// Getter for [`Administrator::can_promote_members`] field. pub fn can_promote_members(&self) -> Option { match &self { - Self::Administrator(Administrator { can_promote_members, .. }) => { - Some(*can_promote_members) - } + Self::Administrator(Administrator { + can_promote_members, + .. + }) => Some(*can_promote_members), Self::Creator(_) | Self::Member | Self::Restricted(_) @@ -265,7 +278,9 @@ impl ChatMemberKind { /// Getter for [`Restricted::can_send_messages`] field. pub fn can_send_messages(&self) -> Option { match &self { - Self::Restricted(Restricted { can_send_messages, .. }) => Some(*can_send_messages), + Self::Restricted(Restricted { + can_send_messages, .. + }) => Some(*can_send_messages), Self::Creator(_) | Self::Administrator(_) | Self::Member @@ -277,9 +292,10 @@ impl ChatMemberKind { /// Getter for [`Restricted::can_send_media_messages`] field. pub fn can_send_media_messages(&self) -> Option { match &self { - Self::Restricted(Restricted { can_send_media_messages, .. }) => { - Some(*can_send_media_messages) - } + Self::Restricted(Restricted { + can_send_media_messages, + .. + }) => Some(*can_send_media_messages), Self::Creator(_) | Self::Administrator(_) | Self::Member @@ -291,9 +307,10 @@ impl ChatMemberKind { /// Getter for [`Restricted::can_send_other_messages`] field. pub fn can_send_other_messages(&self) -> Option { match &self { - Self::Restricted(Restricted { can_send_other_messages, .. }) => { - Some(*can_send_other_messages) - } + Self::Restricted(Restricted { + can_send_other_messages, + .. + }) => Some(*can_send_other_messages), Self::Creator(_) | Self::Administrator(_) | Self::Member @@ -305,9 +322,10 @@ impl ChatMemberKind { /// Getter for [`Restricted::can_add_web_page_previews`] field. pub fn can_add_web_page_previews(&self) -> Option { match &self { - Self::Restricted(Restricted { can_add_web_page_previews, .. }) => { - Some(*can_add_web_page_previews) - } + Self::Restricted(Restricted { + can_add_web_page_previews, + .. + }) => Some(*can_add_web_page_previews), Self::Creator(_) | Self::Administrator(_) | Self::Member diff --git a/src/types/encrypted_credentials.rs b/src/types/encrypted_credentials.rs index 332273e7..5e3562a5 100644 --- a/src/types/encrypted_credentials.rs +++ b/src/types/encrypted_credentials.rs @@ -37,7 +37,11 @@ impl EncryptedCredentials { S2: Into, S3: Into, { - Self { data: data.into(), hash: hash.into(), secret: secret.into() } + Self { + data: data.into(), + hash: hash.into(), + secret: secret.into(), + } } pub fn data(mut self, val: S) -> Self diff --git a/src/types/encrypted_passport_element.rs b/src/types/encrypted_passport_element.rs index 98ed43a2..584e9d9a 100644 --- a/src/types/encrypted_passport_element.rs +++ b/src/types/encrypted_passport_element.rs @@ -24,7 +24,10 @@ impl EncryptedPassportElement { where S: Into, { - Self { hash: hash.into(), kind } + Self { + hash: hash.into(), + kind, + } } pub fn hash(mut self, val: S) -> Self @@ -140,7 +143,12 @@ impl EncryptedPassportElementPassport { where S: Into, { - Self { data: data.into(), front_side, selfie, translation: None } + Self { + data: data.into(), + front_side, + selfie, + translation: None, + } } pub fn data(mut self, val: S) -> Self @@ -233,7 +241,13 @@ impl EncryptedPassportElementDriverLicense { where S: Into, { - Self { data: data.into(), front_side, reverse_side, selfie, translation: None } + Self { + data: data.into(), + front_side, + reverse_side, + selfie, + translation: None, + } } pub fn data(mut self, val: S) -> Self @@ -330,7 +344,13 @@ impl EncryptedPassportElementIdentityCard { where S: Into, { - Self { data: data.into(), front_side, reverse_side, selfie, translation: None } + Self { + data: data.into(), + front_side, + reverse_side, + selfie, + translation: None, + } } pub fn data(mut self, val: S) -> Self @@ -413,7 +433,12 @@ impl EncryptedPassportElementInternalPassport { where S: Into, { - Self { data: data.into(), front_side, selfie, translation: None } + Self { + data: data.into(), + front_side, + selfie, + translation: None, + } } pub fn data(mut self, val: S) -> Self @@ -505,7 +530,10 @@ impl EncryptedPassportElementUtilityBill { where F: Into>, { - Self { files: files.into(), translation: None } + Self { + files: files.into(), + translation: None, + } } pub fn files

(mut self, val: P) -> Self @@ -556,7 +584,10 @@ impl EncryptedPassportElementBankStatement { where F: Into>, { - Self { files: files.into(), translation: None } + Self { + files: files.into(), + translation: None, + } } pub fn files

(mut self, val: P) -> Self @@ -607,7 +638,10 @@ impl EncryptedPassportElementRentalAgreement { where F: Into>, { - Self { files: files.into(), translation: None } + Self { + files: files.into(), + translation: None, + } } pub fn files

(mut self, val: P) -> Self @@ -658,7 +692,10 @@ impl EncryptedPassportElementPassportRegistration { where F: Into>, { - Self { files: files.into(), translation: None } + Self { + files: files.into(), + translation: None, + } } pub fn files

(mut self, val: P) -> Self @@ -709,7 +746,10 @@ impl EncryptedPassportElementTemporaryRegistration { where F: Into>, { - Self { files: files.into(), translation: None } + Self { + files: files.into(), + translation: None, + } } pub fn files

(mut self, val: P) -> Self @@ -742,7 +782,9 @@ impl EncryptedPassportElementPhoneNumber { where S: Into, { - Self { phone_number: phone_number.into() } + Self { + phone_number: phone_number.into(), + } } pub fn phone_number(mut self, val: S) -> Self @@ -766,7 +808,9 @@ impl EncryptedPassportElementEmail { where S: Into, { - Self { email: email.into() } + Self { + email: email.into(), + } } pub fn email(mut self, val: S) -> Self diff --git a/src/types/game.rs b/src/types/game.rs index 477f7988..ca3f4e7d 100644 --- a/src/types/game.rs +++ b/src/types/game.rs @@ -20,8 +20,6 @@ pub struct Game { /// Photo that will be displayed in the game message in chats. pub photo: Vec, - // FIXME(waffle): SetGameScore method is missing for some reason O_o - #[allow(broken_intra_doc_links)] /// Brief description of the game or high scores included in the game /// message. Can be automatically edited to include current high scores /// for the game when the bot calls [`SetGameScore`], or manually diff --git a/src/types/game_high_score.rs b/src/types/game_high_score.rs index 4d64e515..57ac5719 100644 --- a/src/types/game_high_score.rs +++ b/src/types/game_high_score.rs @@ -19,7 +19,11 @@ pub struct GameHighScore { impl GameHighScore { pub fn new(position: u32, user: User, score: u32) -> Self { - Self { position, user, score } + Self { + position, + user, + score, + } } pub fn position(mut self, val: u32) -> Self { diff --git a/src/types/inline_keyboard_button.rs b/src/types/inline_keyboard_button.rs index 8f65d04b..86a5af04 100644 --- a/src/types/inline_keyboard_button.rs +++ b/src/types/inline_keyboard_button.rs @@ -18,7 +18,10 @@ impl InlineKeyboardButton { where S: Into, { - Self { text: text.into(), kind } + Self { + text: text.into(), + kind, + } } pub fn text(mut self, val: S) -> Self @@ -105,11 +108,17 @@ pub enum InlineKeyboardButtonKind { /// ``` impl InlineKeyboardButton { pub fn url(text: String, url: String) -> InlineKeyboardButton { - InlineKeyboardButton { text, kind: InlineKeyboardButtonKind::Url(url) } + InlineKeyboardButton { + text, + kind: InlineKeyboardButtonKind::Url(url), + } } pub fn callback(text: String, callback_data: String) -> InlineKeyboardButton { - InlineKeyboardButton { text, kind: InlineKeyboardButtonKind::CallbackData(callback_data) } + InlineKeyboardButton { + text, + kind: InlineKeyboardButtonKind::CallbackData(callback_data), + } } pub fn switch_inline_query(text: String, switch_inline_query: String) -> InlineKeyboardButton { diff --git a/src/types/inline_keyboard_markup.rs b/src/types/inline_keyboard_markup.rs index d0e56444..25618524 100644 --- a/src/types/inline_keyboard_markup.rs +++ b/src/types/inline_keyboard_markup.rs @@ -35,7 +35,9 @@ impl InlineKeyboardMarkup { I1: Into>, I2: Into>, { - Self { inline_keyboard: inline_keyboard.into().into_iter().map(Into::into).collect() } + Self { + inline_keyboard: inline_keyboard.into().into_iter().map(Into::into).collect(), + } } pub fn inline_keyboard(mut self, val: I1) -> Self @@ -73,7 +75,9 @@ mod tests { let markup = InlineKeyboardMarkup::default().append_row(vec![button1.clone(), button2.clone()]); - let expected = InlineKeyboardMarkup { inline_keyboard: vec![vec![button1, button2]] }; + let expected = InlineKeyboardMarkup { + inline_keyboard: vec![vec![button1, button2]], + }; assert_eq!(markup, expected); } @@ -87,7 +91,9 @@ mod tests { .append_row(vec![button1.clone()]) .append_to_row(button2.clone(), 0); - let expected = InlineKeyboardMarkup { inline_keyboard: vec![vec![button1, button2]] }; + let expected = InlineKeyboardMarkup { + inline_keyboard: vec![vec![button1, button2]], + }; assert_eq!(markup, expected); } @@ -101,7 +107,9 @@ mod tests { .append_row(vec![button1.clone()]) .append_to_row(button2.clone(), 1); - let expected = InlineKeyboardMarkup { inline_keyboard: vec![vec![button1], vec![button2]] }; + let expected = InlineKeyboardMarkup { + inline_keyboard: vec![vec![button1], vec![button2]], + }; assert_eq!(markup, expected); } diff --git a/src/types/inline_query.rs b/src/types/inline_query.rs index 9411822d..0eba0270 100644 --- a/src/types/inline_query.rs +++ b/src/types/inline_query.rs @@ -34,7 +34,13 @@ impl InlineQuery { S2: Into, S3: Into, { - Self { id: id.into(), from, location: None, query: query.into(), offset: offset.into() } + Self { + id: id.into(), + from, + location: None, + query: query.into(), + offset: offset.into(), + } } pub fn id(mut self, val: S) -> Self diff --git a/src/types/inline_query_result_game.rs b/src/types/inline_query_result_game.rs index 0436074b..9e6c074e 100644 --- a/src/types/inline_query_result_game.rs +++ b/src/types/inline_query_result_game.rs @@ -28,7 +28,11 @@ impl InlineQueryResultGame { S1: Into, S2: Into, { - Self { id: id.into(), game_short_name: game_short_name.into(), reply_markup: None } + Self { + id: id.into(), + game_short_name: game_short_name.into(), + reply_markup: None, + } } pub fn id(mut self, val: S) -> Self diff --git a/src/types/input_file.rs b/src/types/input_file.rs index 6bd0b171..eb9bd600 100644 --- a/src/types/input_file.rs +++ b/src/types/input_file.rs @@ -8,7 +8,10 @@ use std::{borrow::Cow, path::PathBuf}; #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum InputFile { File(PathBuf), - Memory { file_name: String, data: Cow<'static, [u8]> }, + Memory { + file_name: String, + data: Cow<'static, [u8]>, + }, Url(String), FileId(String), } @@ -26,7 +29,10 @@ impl InputFile { S: Into, D: Into>, { - Self::Memory { file_name: file_name.into(), data: data.into() } + Self::Memory { + file_name: file_name.into(), + data: data.into(), + } } pub fn url(url: T) -> Self @@ -100,7 +106,11 @@ impl InputFile { match self { Self::File(path_to_file) => { - let file_name = path_to_file.file_name().unwrap().to_string_lossy().into_owned(); + let file_name = path_to_file + .file_name() + .unwrap() + .to_string_lossy() + .into_owned(); let file = FramedRead::new(tokio::fs::File::open(path_to_file).await?, FileDecoder); diff --git a/src/types/input_media.rs b/src/types/input_media.rs index 603a4a16..a5d3dd84 100644 --- a/src/types/input_media.rs +++ b/src/types/input_media.rs @@ -39,7 +39,11 @@ pub struct InputMediaPhoto { impl InputMediaPhoto { pub fn new(media: InputFile) -> Self { - Self { media, caption: None, parse_mode: None } + Self { + media, + caption: None, + parse_mode: None, + } } pub fn media(mut self, val: InputFile) -> Self { @@ -374,7 +378,12 @@ pub struct InputMediaDocument { impl InputMediaDocument { pub fn new(media: InputFile) -> Self { - Self { media, thumb: None, caption: None, parse_mode: None } + Self { + media, + thumb: None, + caption: None, + parse_mode: None, + } } pub fn thumb(mut self, val: InputFile) -> Self { diff --git a/src/types/input_message_content.rs b/src/types/input_message_content.rs index 10d1d2f3..169fdd1c 100644 --- a/src/types/input_message_content.rs +++ b/src/types/input_message_content.rs @@ -39,7 +39,11 @@ impl InputMessageContentText { where S: Into, { - Self { message_text: message_text.into(), parse_mode: None, disable_web_page_preview: None } + Self { + message_text: message_text.into(), + parse_mode: None, + disable_web_page_preview: None, + } } pub fn message_text(mut self, val: S) -> Self @@ -79,7 +83,11 @@ pub struct InputMessageContentLocation { impl InputMessageContentLocation { pub fn new(latitude: f64, longitude: f64) -> Self { - Self { latitude, longitude, live_period: None } + Self { + latitude, + longitude, + live_period: None, + } } pub fn latitude(mut self, val: f64) -> Self { diff --git a/src/types/keyboard_button.rs b/src/types/keyboard_button.rs index b245a5a2..f25f4f52 100644 --- a/src/types/keyboard_button.rs +++ b/src/types/keyboard_button.rs @@ -29,7 +29,10 @@ impl KeyboardButton { where T: Into, { - Self { text: text.into(), request: None } + Self { + text: text.into(), + request: None, + } } pub fn request(mut self, val: T) -> Self @@ -77,15 +80,24 @@ impl<'de> Deserialize<'de> for ButtonRequest { { let raw = RawRequest::deserialize(deserializer)?; match raw { - RawRequest { contact: Some(_), location: Some(_), poll: Some(_) } => { - Err(D::Error::custom( - "`request_contact` and `request_location` fields are mutually exclusive, but \ - both were provided", - )) - } - RawRequest { contact: Some(_), .. } => Ok(Self::Contact), - RawRequest { location: Some(_), .. } => Ok(Self::Location), - RawRequest { poll: Some(poll_type), .. } => Ok(Self::KeyboardButtonPollType(poll_type)), + RawRequest { + contact: Some(_), + location: Some(_), + poll: Some(_), + } => Err(D::Error::custom( + "`request_contact` and `request_location` fields are mutually exclusive, but both \ + were provided", + )), + RawRequest { + contact: Some(_), .. + } => Ok(Self::Contact), + RawRequest { + location: Some(_), .. + } => Ok(Self::Location), + RawRequest { + poll: Some(poll_type), + .. + } => Ok(Self::KeyboardButtonPollType(poll_type)), _ => Err(D::Error::custom( "Either one of `request_contact` and `request_location` fields is required", )), @@ -99,16 +111,24 @@ impl Serialize for ButtonRequest { S: Serializer, { match self { - Self::Contact => { - RawRequest { contact: Some(True), location: None, poll: None }.serialize(serializer) + Self::Contact => RawRequest { + contact: Some(True), + location: None, + poll: None, } - Self::Location => { - RawRequest { contact: None, location: Some(True), poll: None }.serialize(serializer) + .serialize(serializer), + Self::Location => RawRequest { + contact: None, + location: Some(True), + poll: None, } - Self::KeyboardButtonPollType(poll_type) => { - RawRequest { contact: None, location: None, poll: Some(poll_type.clone()) } - .serialize(serializer) + .serialize(serializer), + Self::KeyboardButtonPollType(poll_type) => RawRequest { + contact: None, + location: None, + poll: Some(poll_type.clone()), } + .serialize(serializer), } } } @@ -119,7 +139,10 @@ mod tests { #[test] fn serialize_no_request() { - let button = KeyboardButton { text: String::from(""), request: None }; + let button = KeyboardButton { + text: String::from(""), + request: None, + }; let expected = r#"{"text":""}"#; let actual = serde_json::to_string(&button).unwrap(); assert_eq!(expected, actual); @@ -127,8 +150,10 @@ mod tests { #[test] fn serialize_request_contact() { - let button = - KeyboardButton { text: String::from(""), request: Some(ButtonRequest::Contact) }; + let button = KeyboardButton { + text: String::from(""), + request: Some(ButtonRequest::Contact), + }; let expected = r#"{"text":"","request_contact":true}"#; let actual = serde_json::to_string(&button).unwrap(); assert_eq!(expected, actual); @@ -137,7 +162,10 @@ mod tests { #[test] fn deserialize_no_request() { let json = r#"{"text":""}"#; - let expected = KeyboardButton { text: String::from(""), request: None }; + let expected = KeyboardButton { + text: String::from(""), + request: None, + }; let actual = serde_json::from_str(json).unwrap(); assert_eq!(expected, actual); } @@ -145,8 +173,10 @@ mod tests { #[test] fn deserialize_request_contact() { let json = r#"{"text":"","request_contact":true}"#; - let expected = - KeyboardButton { text: String::from(""), request: Some(ButtonRequest::Contact) }; + let expected = KeyboardButton { + text: String::from(""), + request: Some(ButtonRequest::Contact), + }; let actual = serde_json::from_str(json).unwrap(); assert_eq!(expected, actual); } diff --git a/src/types/keyboard_button_poll_type.rs b/src/types/keyboard_button_poll_type.rs index 40289dd0..07c7ddc6 100644 --- a/src/types/keyboard_button_poll_type.rs +++ b/src/types/keyboard_button_poll_type.rs @@ -10,7 +10,9 @@ impl KeyboardButtonPollType { where S: Into, { - Self { poll_type: poll_type.into() } + Self { + poll_type: poll_type.into(), + } } pub fn poll_type(mut self, val: S) -> Self diff --git a/src/types/label_price.rs b/src/types/label_price.rs index 704aa5c6..0a5a2f72 100644 --- a/src/types/label_price.rs +++ b/src/types/label_price.rs @@ -24,7 +24,10 @@ impl LabeledPrice { where S: Into, { - Self { label: label.into(), amount } + Self { + label: label.into(), + amount, + } } pub fn label(mut self, val: S) -> Self @@ -47,7 +50,10 @@ mod tests { #[test] fn serialize() { - let labeled_price = LabeledPrice { label: "Label".to_string(), amount: 60 }; + let labeled_price = LabeledPrice { + label: "Label".to_string(), + amount: 60, + }; let expected = r#"{"label":"Label","amount":60}"#; let actual = serde_json::to_string(&labeled_price).unwrap(); assert_eq!(actual, expected); diff --git a/src/types/location.rs b/src/types/location.rs index 8c37f4fb..7fd1a71d 100644 --- a/src/types/location.rs +++ b/src/types/location.rs @@ -12,7 +12,10 @@ pub struct Location { impl Location { pub fn new(longitude: f64, latitude: f64) -> Self { - Self { longitude, latitude } + Self { + longitude, + latitude, + } } pub fn latitude(mut self, val: f64) -> Self { diff --git a/src/types/mask_position.rs b/src/types/mask_position.rs index cd0d0828..bf52eccf 100644 --- a/src/types/mask_position.rs +++ b/src/types/mask_position.rs @@ -29,7 +29,12 @@ impl MaskPosition { where S: Into, { - Self { point: point.into(), x_shift, y_shift, scale } + Self { + point: point.into(), + x_shift, + y_shift, + scale, + } } pub fn point(mut self, val: S) -> Self diff --git a/src/types/me.rs b/src/types/me.rs index bf07b50f..3f7d1c32 100644 --- a/src/types/me.rs +++ b/src/types/me.rs @@ -28,7 +28,12 @@ impl Me { can_read_all_group_messages: bool, supports_inline_queries: bool, ) -> Self { - Self { user, can_join_groups, can_read_all_group_messages, supports_inline_queries } + Self { + user, + can_join_groups, + can_read_all_group_messages, + supports_inline_queries, + } } pub fn user(mut self, val: User) -> Self { diff --git a/src/types/message.rs b/src/types/message.rs index 8a6890a1..1e13e158 100644 --- a/src/types/message.rs +++ b/src/types/message.rs @@ -33,7 +33,13 @@ pub struct Message { impl Message { pub fn new(id: i32, date: i32, chat: Chat, kind: MessageKind) -> Self { - Self { id, date, chat, kind, via_bot: None } + Self { + id, + date, + chat, + kind, + via_bot: None, + } } pub fn id(mut self, val: i32) -> Self { @@ -105,7 +111,13 @@ pub struct MessageCommon { impl MessageCommon { pub fn new(forward_kind: ForwardKind, media_kind: MediaKind) -> Self { - Self { from: None, forward_kind, edit_date: None, media_kind, reply_markup: None } + Self { + from: None, + forward_kind, + edit_date: None, + media_kind, + reply_markup: None, + } } pub fn from(mut self, val: User) -> Self { @@ -147,7 +159,9 @@ impl MessageNewChatMembers { where N: Into>, { - Self { new_chat_members: new_chat_members.into() } + Self { + new_chat_members: new_chat_members.into(), + } } pub fn new_chat_members(mut self, val: N) -> Self @@ -171,7 +185,9 @@ impl MessageLeftChatMember { where N: Into, { - Self { left_chat_member: left_chat_member.into() } + Self { + left_chat_member: left_chat_member.into(), + } } pub fn left_chat_member(mut self, val: N) -> Self @@ -194,7 +210,9 @@ impl MessageNewChatTitle { where N: Into, { - Self { new_chat_title: new_chat_title.into() } + Self { + new_chat_title: new_chat_title.into(), + } } pub fn new_chat_title(mut self, val: N) -> Self @@ -217,7 +235,9 @@ impl MessageNewChatPhoto { where N: Into>, { - Self { new_chat_photo: new_chat_photo.into() } + Self { + new_chat_photo: new_chat_photo.into(), + } } pub fn new_chat_photo(mut self, val: N) -> Self @@ -306,7 +326,10 @@ pub struct MessageMigrate { impl MessageMigrate { pub fn new(migrate_to_chat_id: i64, migrate_from_chat_id: i64) -> Self { - Self { migrate_to_chat_id, migrate_from_chat_id } + Self { + migrate_to_chat_id, + migrate_from_chat_id, + } } pub fn migrate_to_chat_id(mut self, val: i64) -> Self { @@ -331,7 +354,9 @@ pub struct MessagePinned { impl MessagePinned { pub fn new(pinned: Message) -> Self { - Self { pinned: Box::new(pinned) } + Self { + pinned: Box::new(pinned), + } } pub fn pinned(mut self, val: Message) -> Self { @@ -395,7 +420,9 @@ impl MessageConnectedWebsite { where S: Into, { - Self { connected_website: connected_website.into() } + Self { + connected_website: connected_website.into(), + } } pub fn connected_website(mut self, val: S) -> Self @@ -457,7 +484,12 @@ pub struct ForwardChannel { impl ForwardChannel { pub fn new(date: i32, chat: Chat, message_id: i32) -> Self { - Self { date, chat, message_id, signature: None } + Self { + date, + chat, + message_id, + signature: None, + } } pub fn date(mut self, val: i32) -> Self { @@ -570,7 +602,12 @@ impl MediaAnimation { where CE: Into>, { - Self { animation, document: (), caption: None, caption_entities: caption_entities.into() } + Self { + animation, + document: (), + caption: None, + caption_entities: caption_entities.into(), + } } pub fn animation(mut self, val: Animation) -> Self { @@ -615,7 +652,11 @@ impl MediaAudio { where CE: Into>, { - Self { audio, caption: None, caption_entities: caption_entities.into() } + Self { + audio, + caption: None, + caption_entities: caption_entities.into(), + } } pub fn audio(mut self, val: Audio) -> Self { @@ -677,7 +718,11 @@ impl MediaDocument { where CE: Into>, { - Self { document, caption: None, caption_entities: caption_entities.into() } + Self { + document, + caption: None, + caption_entities: caption_entities.into(), + } } pub fn document(mut self, val: Document) -> Self { @@ -857,7 +902,10 @@ impl MediaText { S: Into, E: Into>, { - Self { text: text.into(), entities: entities.into() } + Self { + text: text.into(), + entities: entities.into(), + } } pub fn text(mut self, val: S) -> Self @@ -978,7 +1026,11 @@ impl MediaVoice { where CE: Into>, { - Self { voice, caption: None, caption_entities: caption_entities.into() } + Self { + voice, + caption: None, + caption_entities: caption_entities.into(), + } } pub fn voice(mut self, val: Voice) -> Self { @@ -1125,7 +1177,10 @@ mod getters { pub fn reply_to_message(&self) -> Option<&Message> { match &self.kind { Common(MessageCommon { - forward_kind: ForwardKind::Origin(ForwardOrigin { reply_to_message, .. }), + forward_kind: + ForwardKind::Origin(ForwardOrigin { + reply_to_message, .. + }), .. }) => reply_to_message.as_ref().map(Deref::deref), _ => None, @@ -1180,27 +1235,45 @@ mod getters { pub fn caption_entities(&self) -> Option<&[MessageEntity]> { match &self.kind { Common(MessageCommon { - media_kind: MediaKind::Animation(MediaAnimation { caption_entities, .. }), + media_kind: + MediaKind::Animation(MediaAnimation { + caption_entities, .. + }), .. }) | Common(MessageCommon { - media_kind: MediaKind::Audio(MediaAudio { caption_entities, .. }), + media_kind: + MediaKind::Audio(MediaAudio { + caption_entities, .. + }), .. }) | Common(MessageCommon { - media_kind: MediaKind::Document(MediaDocument { caption_entities, .. }), + media_kind: + MediaKind::Document(MediaDocument { + caption_entities, .. + }), .. }) | Common(MessageCommon { - media_kind: MediaKind::Photo(MediaPhoto { caption_entities, .. }), + media_kind: + MediaKind::Photo(MediaPhoto { + caption_entities, .. + }), .. }) | Common(MessageCommon { - media_kind: MediaKind::Video(MediaVideo { caption_entities, .. }), + media_kind: + MediaKind::Video(MediaVideo { + caption_entities, .. + }), .. }) | Common(MessageCommon { - media_kind: MediaKind::Voice(MediaVoice { caption_entities, .. }), + media_kind: + MediaKind::Voice(MediaVoice { + caption_entities, .. + }), .. }) => Some(caption_entities), _ => None, @@ -1408,32 +1481,37 @@ mod getters { pub fn super_group_chat_created(&self) -> Option { match &self.kind { - SupergroupChatCreated(MessageSupergroupChatCreated { supergroup_chat_created }) => { - Some(*supergroup_chat_created) - } + SupergroupChatCreated(MessageSupergroupChatCreated { + supergroup_chat_created, + }) => Some(*supergroup_chat_created), _ => None, } } pub fn channel_chat_created(&self) -> Option { match &self.kind { - ChannelChatCreated(MessageChannelChatCreated { channel_chat_created }) => { - Some(*channel_chat_created) - } + ChannelChatCreated(MessageChannelChatCreated { + channel_chat_created, + }) => Some(*channel_chat_created), _ => None, } } pub fn migrate_to_chat_id(&self) -> Option { match &self.kind { - Migrate(MessageMigrate { migrate_to_chat_id, .. }) => Some(*migrate_to_chat_id), + Migrate(MessageMigrate { + migrate_to_chat_id, .. + }) => Some(*migrate_to_chat_id), _ => None, } } pub fn migrate_from_chat_id(&self) -> Option { match &self.kind { - Migrate(MessageMigrate { migrate_from_chat_id, .. }) => Some(*migrate_from_chat_id), + Migrate(MessageMigrate { + migrate_from_chat_id, + .. + }) => Some(*migrate_from_chat_id), _ => None, } } @@ -1490,13 +1568,17 @@ impl Message { pub fn url(&self) -> Option { match &self.chat.kind { ChatKind::Public(ChatPublic { - kind: PublicChatKind::Channel(PublicChatChannel { username: Some(username) }), + kind: + PublicChatKind::Channel(PublicChatChannel { + username: Some(username), + }), .. }) | ChatKind::Public(ChatPublic { kind: PublicChatKind::Supergroup(PublicChatSupergroup { - username: Some(username), .. + username: Some(username), + .. }), .. }) => Some( diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs index 283199d3..79876cf0 100644 --- a/src/types/message_entity.rs +++ b/src/types/message_entity.rs @@ -21,7 +21,11 @@ pub struct MessageEntity { impl MessageEntity { pub fn new(kind: MessageEntityKind, offset: usize, length: usize) -> Self { - Self { kind, offset, length } + Self { + kind, + offset, + length, + } } pub fn kind(mut self, val: MessageEntityKind) -> Self { @@ -82,7 +86,9 @@ mod tests { assert_eq!( MessageEntity { - kind: MessageEntityKind::TextLink { url: "ya.ru".into() }, + kind: MessageEntityKind::TextLink { + url: "ya.ru".into() + }, offset: 1, length: 2, }, @@ -99,7 +105,9 @@ mod tests { assert_eq!( MessageEntity { - kind: MessageEntityKind::Pre { language: Some("rust".to_string()) }, + kind: MessageEntityKind::Pre { + language: Some("rust".to_string()) + }, offset: 1, length: 2, }, @@ -143,7 +151,9 @@ mod tests { username: None, language_code: None, }), - forward_kind: ForwardKind::Origin(ForwardOrigin { reply_to_message: None }), + forward_kind: ForwardKind::Origin(ForwardOrigin { + reply_to_message: None, + }), edit_date: None, media_kind: MediaKind::Text(MediaText { text: "no yes no".to_string(), diff --git a/src/types/non_telegram_types/non_strict_vec.rs b/src/types/non_telegram_types/non_strict_vec.rs index 0ae479c6..37bc46d7 100644 --- a/src/types/non_telegram_types/non_strict_vec.rs +++ b/src/types/non_telegram_types/non_strict_vec.rs @@ -10,7 +10,11 @@ pub struct NonStrictVec(pub Vec From> for NonStrictVec { fn from(vec: Vec) -> Self { - Self(vec.into_iter().map(|val| from_value(val.clone()).map_err(|e| (val, e))).collect()) + Self( + vec.into_iter() + .map(|val| from_value(val.clone()).map_err(|e| (val, e))) + .collect(), + ) } } diff --git a/src/types/passport_data.rs b/src/types/passport_data.rs index c2abcd49..c34f97ea 100644 --- a/src/types/passport_data.rs +++ b/src/types/passport_data.rs @@ -21,7 +21,10 @@ impl PassportData { where E: Into>, { - Self { data: data.into(), credentials } + Self { + data: data.into(), + credentials, + } } pub fn data(mut self, val: E) -> Self diff --git a/src/types/passport_element_error.rs b/src/types/passport_element_error.rs index 4ccb9335..da96fba1 100644 --- a/src/types/passport_element_error.rs +++ b/src/types/passport_element_error.rs @@ -18,7 +18,10 @@ impl PassportElementError { where S: Into, { - Self { message: message.into(), kind } + Self { + message: message.into(), + kind, + } } pub fn message(mut self, val: S) -> Self @@ -94,7 +97,11 @@ impl PassportElementErrorDataField { S1: Into, S2: Into, { - Self { r#type, field_name: field_name.into(), data_hash: data_hash.into() } + Self { + r#type, + field_name: field_name.into(), + data_hash: data_hash.into(), + } } pub fn r#type(mut self, val: PassportElementErrorDataFieldType) -> Self { @@ -140,7 +147,10 @@ impl PassportElementErrorFrontSide { where S: Into, { - Self { r#type, file_hash: file_hash.into() } + Self { + r#type, + file_hash: file_hash.into(), + } } pub fn r#type(mut self, val: PassportElementErrorFrontSideType) -> Self { @@ -178,7 +188,10 @@ impl PassportElementErrorReverseSide { where S: Into, { - Self { r#type, file_hash: file_hash.into() } + Self { + r#type, + file_hash: file_hash.into(), + } } pub fn r#type(mut self, val: PassportElementErrorReverseSideType) -> Self { @@ -214,7 +227,10 @@ impl PassportElementErrorSelfie { where S: Into, { - Self { r#type, file_hash: file_hash.into() } + Self { + r#type, + file_hash: file_hash.into(), + } } pub fn r#type(mut self, val: PassportElementErrorSelfieType) -> Self { @@ -251,7 +267,10 @@ impl PassportElementErrorFile { where S: Into, { - Self { r#type, file_hash: file_hash.into() } + Self { + r#type, + file_hash: file_hash.into(), + } } pub fn r#type(mut self, val: PassportElementErrorFileType) -> Self { @@ -288,7 +307,10 @@ impl PassportElementErrorFiles { where S: Into>, { - Self { r#type, file_hashes: file_hashes.into() } + Self { + r#type, + file_hashes: file_hashes.into(), + } } pub fn r#type(mut self, val: PassportElementErrorFilesType) -> Self { @@ -326,7 +348,10 @@ impl PassportElementErrorTranslationFile { where S: Into, { - Self { r#type, file_hash: file_hash.into() } + Self { + r#type, + file_hash: file_hash.into(), + } } pub fn r#type(mut self, val: PassportElementErrorTranslationFileType) -> Self { @@ -363,7 +388,10 @@ impl PassportElementErrorTranslationFiles { where S: Into>, { - Self { r#type, file_hashes: file_hashes.into() } + Self { + r#type, + file_hashes: file_hashes.into(), + } } pub fn r#type(mut self, val: PassportElementErrorTranslationFilesType) -> Self { @@ -400,7 +428,10 @@ impl PassportElementErrorUnspecified { where S: Into, { - Self { r#type, element_hash: file_hash.into() } + Self { + r#type, + element_hash: file_hash.into(), + } } pub fn r#type(mut self, val: PassportElementErrorUnspecifiedType) -> Self { diff --git a/src/types/poll.rs b/src/types/poll.rs index 54020554..8176aa5b 100644 --- a/src/types/poll.rs +++ b/src/types/poll.rs @@ -187,7 +187,10 @@ impl PollOption { where S: Into, { - Self { text: text.into(), voter_count } + Self { + text: text.into(), + voter_count, + } } pub fn text(mut self, val: S) -> Self diff --git a/src/types/poll_answer.rs b/src/types/poll_answer.rs index ac8c2a6b..befeb420 100644 --- a/src/types/poll_answer.rs +++ b/src/types/poll_answer.rs @@ -21,7 +21,11 @@ impl PollAnswer { S: Into, O: Into>, { - Self { poll_id: poll_id.into(), user, option_ids: option_ids.into() } + Self { + poll_id: poll_id.into(), + user, + option_ids: option_ids.into(), + } } pub fn poll_id(mut self, val: S) -> Self diff --git a/src/types/shipping_option.rs b/src/types/shipping_option.rs index af13f5ce..af154d6b 100644 --- a/src/types/shipping_option.rs +++ b/src/types/shipping_option.rs @@ -24,7 +24,11 @@ impl ShippingOption { S2: Into, P: Into>, { - Self { id: id.into(), title: title.into(), prices: prices.into() } + Self { + id: id.into(), + title: title.into(), + prices: prices.into(), + } } pub fn id(mut self, val: S) -> Self @@ -61,7 +65,10 @@ mod tests { let shipping_option = ShippingOption { id: "0".to_string(), title: "Option".to_string(), - prices: vec![LabeledPrice { label: "Label".to_string(), amount: 60 }], + prices: vec![LabeledPrice { + label: "Label".to_string(), + amount: 60, + }], }; let expected = r#"{"id":"0","title":"Option","prices":[{"label":"Label","amount":60}]}"#; let actual = serde_json::to_string(&shipping_option).unwrap(); diff --git a/src/types/shipping_query.rs b/src/types/shipping_query.rs index 06ff5985..97b0fd27 100644 --- a/src/types/shipping_query.rs +++ b/src/types/shipping_query.rs @@ -31,7 +31,12 @@ impl ShippingQuery { S1: Into, S2: Into, { - Self { id: id.into(), from, invoice_payload: invoice_payload.into(), shipping_address } + Self { + id: id.into(), + from, + invoice_payload: invoice_payload.into(), + shipping_address, + } } pub fn id(mut self, val: S) -> Self diff --git a/src/types/update.rs b/src/types/update.rs index 8aa3992f..51caf7ba 100644 --- a/src/types/update.rs +++ b/src/types/update.rs @@ -175,7 +175,9 @@ mod test { username: Some(String::from("WaffleLapkin")), language_code: Some(String::from("en")), }), - forward_kind: ForwardKind::Origin(ForwardOrigin { reply_to_message: None }), + forward_kind: ForwardKind::Origin(ForwardOrigin { + reply_to_message: None, + }), edit_date: None, media_kind: MediaKind::Text(MediaText { text: String::from("hello there"), diff --git a/src/types/user_profile_photos.rs b/src/types/user_profile_photos.rs index 59935efc..05dec195 100644 --- a/src/types/user_profile_photos.rs +++ b/src/types/user_profile_photos.rs @@ -20,7 +20,10 @@ impl UserProfilePhotos { P1: Into>, P2: Into>, { - Self { total_count, photos: photos.into().into_iter().map(Into::into).collect() } + Self { + total_count, + photos: photos.into().into_iter().map(Into::into).collect(), + } } pub fn total_count(mut self, val: u32) -> Self { From cf3cd63c9258c916d49b1c56e84ef3e8fca81ea3 Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 28 Dec 2020 21:03:33 +0300 Subject: [PATCH 178/755] fix doc tests --- src/requests/request.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/requests/request.rs b/src/requests/request.rs index cdde179b..b719abf5 100644 --- a/src/requests/request.rs +++ b/src/requests/request.rs @@ -42,17 +42,17 @@ pub trait Request: HasPayload { /// ``` /// # async { /// use teloxide_core::{ - /// bot::Bot, - /// methods::GetMe, - /// requests::{Request, RequestJson}, + /// payloads::GetMe, + /// requests::{JsonRequest, Request}, /// types::User, + /// Bot, /// }; /// /// let bot = Bot::new("TOKEN"); /// let method = GetMe::new(); /// let request = JsonRequest::new(bot, method); /// let _: User = request.send().await.unwrap(); - /// # } + /// # }; /// ``` fn send(self) -> Self::Send; @@ -69,17 +69,17 @@ pub trait Request: HasPayload { /// ## Examples /// ``` /// # async { - /// use teloxide_core::prelude::*; + /// use teloxide_core::{prelude::*, requests::Request, Bot}; /// /// let bot = Bot::new("TOKEN"); - /// # let chat_ids = vec![1, 2, 3, 4].into_iter().map(Into::into); + /// # let chat_ids = vec![1i64, 2, 3, 4].into_iter().map(Into::into); /// /// let mut req = bot.send_message(0, "Hi there!"); /// for chat_id in chat_ids { /// req.chat_id = chat_id; /// req.send_ref().await.unwrap(); /// } - /// # } + /// # }; /// ``` fn send_ref(&self) -> Self::SendRef; } From 114d1c3efbaedc0e47e1023437cf7030b9c345a3 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Wed, 30 Dec 2020 17:03:29 +0300 Subject: [PATCH 179/755] Update src/bot/mod.rs Co-authored-by: Temirkhan Myrzamadi --- src/bot/mod.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 5a7654a3..3620b767 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -100,10 +100,10 @@ impl Bot { Self::with_client(&get_env(TELOXIDE_TOKEN), client) } - /// Sets custom api url. + /// Sets a custom API URL. /// - /// For example you can run your own [Telegram bot API server][tbas] and set - /// it's url using this method. + /// For example, you can run your own [Telegram bot API server][tbas] and set + /// its URL using this method. /// /// [tbas]: https://github.com/tdlib/telegram-bot-api /// @@ -116,10 +116,9 @@ impl Bot { /// }; /// /// # async { - /// // Setup bot /// let url = reqwest::Url::parse("https://localhost/tbas").unwrap(); /// let bot = Bot::new("TOKEN").set_api_url(url); - /// // All methods will use "https://localhost/tbas" as an api url + /// // From now all methods will use "https://localhost/tbas" as an API URL. /// bot.get_me().send().await /// # }; /// ``` From 6004d9189c091c3c35a7b3a1f6b549abc26a8e8b Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 30 Dec 2020 17:06:52 +0300 Subject: [PATCH 180/755] fmt --- src/bot/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 3620b767..00032315 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -102,8 +102,8 @@ impl Bot { /// Sets a custom API URL. /// - /// For example, you can run your own [Telegram bot API server][tbas] and set - /// its URL using this method. + /// For example, you can run your own [Telegram bot API server][tbas] and + /// set its URL using this method. /// /// [tbas]: https://github.com/tdlib/telegram-bot-api /// From 71d90002cebdd0bd4514fb0d61baaa0c020e2e84 Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 7 Jan 2021 15:58:51 +0300 Subject: [PATCH 181/755] update dependencies --- Cargo.toml | 13 ++++++------- src/adaptors/throttle.rs | 33 ++++++++++++++++++--------------- src/bot/mod.rs | 2 +- src/net/request.rs | 2 +- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e9a9a648..3003d6ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,12 +17,11 @@ authors = [ [dependencies] futures = "0.3.5" -tokio = { version = "0.2.21", features = ["fs", "stream"] } -tokio-util = "0.3.1" -pin-project = "0.4.23" -bytes = "0.5.5" -async-trait = "0.1.36" -reqwest = { version = "0.10.6", features = ["json", "stream"] } +tokio = { version = "1.0.1", features = ["fs"] } +tokio-util = "0.6.0" +pin-project = "1.0.3" +bytes = "1.0.0" +reqwest = { version = "0.11.0", features = ["json", "stream", "multipart"] } serde = { version = "1.0.114", features = ["derive"] } serde_json = "1.0.55" @@ -32,7 +31,7 @@ uuid = { version = "0.8.1", features = ["v4"] } # for attaching input files derive_more = "0.99.9" mime = "0.3.16" thiserror = "1.0.20" -once_cell = "1.4.0" +once_cell = "1.5.0" never = "0.1.0" # FIXME(waffle): use crates.io once published diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index a71fafa5..75d79a80 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -6,14 +6,14 @@ use std::{ time::{Duration, Instant}, }; -use futures::task::{Context, Poll}; +use futures::{ + task::{Context, Poll}, + FutureExt, +}; use never::Never; -use tokio::{ - sync::{ - mpsc::{self, error::TryRecvError}, - oneshot::{channel, Receiver, Sender}, - }, - time::delay_for, +use tokio::sync::{ + mpsc, + oneshot::{channel, Receiver, Sender}, }; use vecrem::VecExt; @@ -182,10 +182,13 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender) // update local queue with latest requests loop { - match queue_rx.try_recv() { - Ok(req) => queue.push(req), - Err(TryRecvError::Empty) => break, - Err(TryRecvError::Closed) => close = true, + // FIXME(waffle): https://github.com/tokio-rs/tokio/issues/3350 + match queue_rx.recv().now_or_never() { + Some(Some(req)) => queue.push(req), + // There are no items in queue + None => break, + // The queue was closed + Some(None) => close = true, } } @@ -256,7 +259,7 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender) if allowed == 0 { hchats_s.clear(); - delay_for(DELAY).await; + tokio::time::sleep(DELAY).await; continue; } @@ -296,7 +299,7 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender) // It's easier to just recompute last second stats, instead of keeping // track of it alongside with minute stats, so we just throw this away. hchats_s.clear(); - delay_for(DELAY).await; + tokio::time::sleep(DELAY).await; } } @@ -764,7 +767,7 @@ mod chan_send { #[cfg(feature = "nightly")] { fn def( - mut sender: mpsc::Sender<(Id, Sender)>, + sender: mpsc::Sender<(Id, Sender)>, val: (Id, Sender), ) -> Inner { async move { sender.send(val).await } @@ -773,7 +776,7 @@ mod chan_send { } #[cfg(not(feature = "nightly"))] { - let mut this = self; + let this = self; return ChanSend(Box::pin(async move { this.send(val).await })); } } diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 00032315..07c78829 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -202,7 +202,7 @@ pub(crate) fn sound_bot() -> ClientBuilder { ClientBuilder::new() .connect_timeout(connect_timeout) .timeout(Duration::from_secs(connect_timeout.as_secs() + timeout + 2)) - .tcp_nodelay_(true) + .tcp_nodelay(true) .default_headers(headers) } diff --git a/src/net/request.rs b/src/net/request.rs index 15fb3d27..b8726001 100644 --- a/src/net/request.rs +++ b/src/net/request.rs @@ -56,7 +56,7 @@ where T: DeserializeOwned, { if response.status().is_server_error() { - tokio::time::delay_for(DELAY_ON_SERVER_ERROR).await; + tokio::time::sleep(DELAY_ON_SERVER_ERROR).await; } serde_json::from_str::>( From 56d546215a2f12b80165d9b827d5118adc0174a9 Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 7 Jan 2021 16:01:46 +0300 Subject: [PATCH 182/755] fix clippy: impl From instead of Into --- src/net/telegram_response.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/net/telegram_response.rs b/src/net/telegram_response.rs index 34242bb3..306d100a 100644 --- a/src/net/telegram_response.rs +++ b/src/net/telegram_response.rs @@ -29,9 +29,9 @@ pub(crate) enum TelegramResponse { }, } -impl Into> for TelegramResponse { - fn into(self) -> Result { - match self { +impl From> for ResponseResult { + fn from(this: TelegramResponse) -> ResponseResult { + match this { TelegramResponse::Ok { response, .. } => Ok(response), TelegramResponse::Err { error, From d3ecca662baccb075a34b3f5702842e3857b22c8 Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 7 Jan 2021 14:58:48 +0300 Subject: [PATCH 183/755] Normalize docs --- Cargo.toml | 2 +- netlify.toml | 2 +- src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3003d6ab..09993956 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,4 +59,4 @@ full = ["throttle", "cache_me", "auto_send"] [package.metadata."docs.rs"] all-features = true -rustdoc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs", "-Znormalize-docs"] diff --git a/netlify.toml b/netlify.toml index be511e64..e55e0d1a 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,6 +1,6 @@ [build] command = "rustup install nightly --profile minimal && cargo +nightly doc --all-features --no-deps && cp -r target/doc _netlify_out" -environment = { RUSTDOCFLAGS= "--cfg docsrs" } +environment = { RUSTDOCFLAGS= "--cfg docsrs -Znormalize-docs" } publish = "_netlify_out" [[redirects]] diff --git a/src/lib.rs b/src/lib.rs index 67231953..d1105eaf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ // // To properly build docs of this crate run // ```console -// $ RUSTDOCFLAGS="--cfg docsrs" cargo doc --open --all-features +// $ RUSTDOCFLAGS="--cfg docsrs -Znormalize-docs" cargo doc --open --all-features // ``` #![forbid(unsafe_code)] #![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg, doc_spotlight))] From 49c3afbc39fa0920d498e9a0d604145b59469bf7 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 8 Jan 2021 00:44:19 +0300 Subject: [PATCH 184/755] fixup send_emoji method --- src/bot/api.rs | 4 ++-- src/local_macros.rs | 6 +++--- src/payloads/add_sticker_to_set.rs | 2 +- src/payloads/answer_callback_query.rs | 8 ++++---- src/payloads/answer_inline_query.rs | 2 +- src/payloads/answer_pre_checkout_query.rs | 2 +- src/payloads/answer_shipping_query.rs | 2 +- src/payloads/create_new_sticker_set.rs | 2 +- src/payloads/delete_chat_photo.rs | 2 +- src/payloads/delete_chat_sticker_set.rs | 2 +- src/payloads/delete_message.rs | 2 +- src/payloads/delete_sticker_from_set.rs | 2 +- src/payloads/delete_webhook.rs | 2 +- src/payloads/edit_message_caption.rs | 2 +- src/payloads/edit_message_caption_inline.rs | 2 +- src/payloads/edit_message_live_location.rs | 4 ++-- src/payloads/edit_message_live_location_inline.rs | 4 ++-- src/payloads/edit_message_media.rs | 2 +- src/payloads/edit_message_media_inline.rs | 2 +- src/payloads/edit_message_reply_markup.rs | 2 +- src/payloads/edit_message_reply_markup_inline.rs | 2 +- src/payloads/edit_message_text.rs | 2 +- src/payloads/edit_message_text_inline.rs | 2 +- src/payloads/export_chat_invite_link.rs | 2 +- src/payloads/forward_message.rs | 2 +- src/payloads/get_chat.rs | 2 +- src/payloads/get_chat_administrators.rs | 2 +- src/payloads/get_chat_member.rs | 2 +- src/payloads/get_chat_members_count.rs | 2 +- src/payloads/get_file.rs | 2 +- src/payloads/get_game_high_scores.rs | 2 +- src/payloads/get_me.rs | 2 +- src/payloads/get_my_commands.rs | 2 +- src/payloads/get_sticker_set.rs | 2 +- src/payloads/get_updates.rs | 2 +- src/payloads/get_user_profile_photos.rs | 2 +- src/payloads/get_webhook_info.rs | 4 ++-- src/payloads/kick_chat_member.rs | 2 +- src/payloads/leave_chat.rs | 2 +- src/payloads/pin_chat_message.rs | 2 +- src/payloads/promote_chat_member.rs | 2 +- src/payloads/restrict_chat_member.rs | 2 +- src/payloads/send_animation.rs | 2 +- src/payloads/send_audio.rs | 2 +- src/payloads/send_chat_action.rs | 10 +++++----- src/payloads/send_contact.rs | 2 +- src/payloads/send_dice.rs | 8 ++++---- src/payloads/send_document.rs | 4 ++-- src/payloads/send_game.rs | 2 +- src/payloads/send_invoice.rs | 2 +- src/payloads/send_location.rs | 4 ++-- src/payloads/send_media_group.rs | 2 +- src/payloads/send_message.rs | 4 ++-- src/payloads/send_photo.rs | 2 +- src/payloads/send_poll.rs | 2 +- src/payloads/send_sticker.rs | 2 +- src/payloads/send_venue.rs | 2 +- src/payloads/send_video.rs | 4 ++-- src/payloads/send_video_note.rs | 6 +++--- src/payloads/send_voice.rs | 4 ++-- src/payloads/set_chat_administrator_custom_title.rs | 2 +- src/payloads/set_chat_description.rs | 2 +- src/payloads/set_chat_permissions.rs | 2 +- src/payloads/set_chat_photo.rs | 2 +- src/payloads/set_chat_sticker_set.rs | 2 +- src/payloads/set_chat_title.rs | 2 +- src/payloads/set_game_score.rs | 2 +- src/payloads/set_game_score_inline.rs | 2 +- src/payloads/set_my_commands.rs | 2 +- src/payloads/set_passport_data_errors.rs | 2 +- src/payloads/set_sticker_position_in_set.rs | 2 +- src/payloads/set_sticker_set_thumb.rs | 2 +- src/payloads/set_webhook.rs | 2 +- src/payloads/stop_message_live_location.rs | 4 ++-- src/payloads/stop_message_live_location_inline.rs | 4 ++-- src/payloads/stop_poll.rs | 2 +- src/payloads/unban_chat_member.rs | 2 +- src/payloads/unpin_chat_message.rs | 2 +- src/payloads/upload_sticker_file.rs | 2 +- src/requests/requester.rs | 10 +++++----- 80 files changed, 109 insertions(+), 109 deletions(-) diff --git a/src/bot/api.rs b/src/bot/api.rs index f03e5cd9..e96f9216 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -299,11 +299,11 @@ impl Requester for Bot { type SendDice = JsonRequest; - fn send_dice(&self, chat_id: C, emoji: crate::types::DiceEmoji) -> Self::SendDice + fn send_dice(&self, chat_id: C) -> Self::SendDice where C: Into, { - Self::SendDice::new(self.clone(), payloads::SendDice::new(chat_id, emoji)) + Self::SendDice::new(self.clone(), payloads::SendDice::new(chat_id)) } type SendChatAction = JsonRequest; diff --git a/src/local_macros.rs b/src/local_macros.rs index 25cffd7f..271d7cad 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -373,7 +373,7 @@ macro_rules! impl_payload { } #[macro_use] -// This macro is auto generated by `cg` (9a82143). +// This macro is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS MACRO**, // edit `cg` instead. macro_rules! requester_forward { @@ -580,9 +580,9 @@ macro_rules! requester_forward { (@method send_dice $body:ident $ty:ident) => { type SendDice = $ty![SendDice]; - fn send_dice(&self, chat_id: C, emoji: DiceEmoji) -> Self::SendDice where C: Into { + fn send_dice(&self, chat_id: C) -> Self::SendDice where C: Into { let this = self; - $body!(send_dice this (chat_id: C, emoji: DiceEmoji)) + $body!(send_dice this (chat_id: C)) } }; (@method send_chat_action $body:ident $ty:ident) => { diff --git a/src/payloads/add_sticker_to_set.rs b/src/payloads/add_sticker_to_set.rs index 1a24bf81..e0eabc79 100644 --- a/src/payloads/add_sticker_to_set.rs +++ b/src/payloads/add_sticker_to_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/answer_callback_query.rs b/src/payloads/answer_callback_query.rs index 1ff7f262..7cb28587 100644 --- a/src/payloads/answer_callback_query.rs +++ b/src/payloads/answer_callback_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -10,8 +10,8 @@ impl_payload! { /// /// >Alternatively, the user can be redirected to the specified Game URL. For this option to work, you must first create a game for your bot via [@Botfather] and accept the terms. Otherwise, you may use links like `t.me/your_bot?start=XXXX` that open your bot with a parameter. /// - /// [@Botfather]: https://t.me/botfather /// [inline keyboards]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [@Botfather]: https://t.me/botfather #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] pub AnswerCallbackQuery (AnswerCallbackQuerySetters) => True { required { @@ -27,9 +27,9 @@ impl_payload! { /// /// Otherwise, you may use links like `t.me/your\_bot?start=XXXX` that open your bot with a parameter. /// - /// [`Game`]: crate::types::Game - /// [@Botfather]: https://t.me/botfather /// [callback_game]: https://core.telegram.org/bots/api#inlinekeyboardbutton + /// [@Botfather]: https://t.me/botfather + /// [`Game`]: crate::types::Game pub url: String [into], /// The maximum amount of time in seconds that the result of the callback query may be cached client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0. pub cache_time: u32, diff --git a/src/payloads/answer_inline_query.rs b/src/payloads/answer_inline_query.rs index 7c970a97..8abddc59 100644 --- a/src/payloads/answer_inline_query.rs +++ b/src/payloads/answer_inline_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/answer_pre_checkout_query.rs b/src/payloads/answer_pre_checkout_query.rs index ec884ba9..a168b72b 100644 --- a/src/payloads/answer_pre_checkout_query.rs +++ b/src/payloads/answer_pre_checkout_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/answer_shipping_query.rs b/src/payloads/answer_shipping_query.rs index b00b29c1..5572c136 100644 --- a/src/payloads/answer_shipping_query.rs +++ b/src/payloads/answer_shipping_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/create_new_sticker_set.rs b/src/payloads/create_new_sticker_set.rs index 1b74097a..2504ce00 100644 --- a/src/payloads/create_new_sticker_set.rs +++ b/src/payloads/create_new_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_chat_photo.rs b/src/payloads/delete_chat_photo.rs index 23165b38..b27666aa 100644 --- a/src/payloads/delete_chat_photo.rs +++ b/src/payloads/delete_chat_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_chat_sticker_set.rs b/src/payloads/delete_chat_sticker_set.rs index ba87b16a..1eaf60be 100644 --- a/src/payloads/delete_chat_sticker_set.rs +++ b/src/payloads/delete_chat_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_message.rs b/src/payloads/delete_message.rs index cf31bf93..22be46fc 100644 --- a/src/payloads/delete_message.rs +++ b/src/payloads/delete_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_sticker_from_set.rs b/src/payloads/delete_sticker_from_set.rs index b432c6ce..f4c4dc22 100644 --- a/src/payloads/delete_sticker_from_set.rs +++ b/src/payloads/delete_sticker_from_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_webhook.rs b/src/payloads/delete_webhook.rs index ebdd2823..cf741a3d 100644 --- a/src/payloads/delete_webhook.rs +++ b/src/payloads/delete_webhook.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_caption.rs b/src/payloads/edit_message_caption.rs index 3b626d28..58608b40 100644 --- a/src/payloads/edit_message_caption.rs +++ b/src/payloads/edit_message_caption.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_caption_inline.rs b/src/payloads/edit_message_caption_inline.rs index 565b7486..1d9d75cf 100644 --- a/src/payloads/edit_message_caption_inline.rs +++ b/src/payloads/edit_message_caption_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_live_location.rs b/src/payloads/edit_message_live_location.rs index c183cb0a..47d1f047 100644 --- a/src/payloads/edit_message_live_location.rs +++ b/src/payloads/edit_message_live_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -26,8 +26,8 @@ impl_payload! { optional { /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/edit_message_live_location_inline.rs b/src/payloads/edit_message_live_location_inline.rs index 79389c4a..99a477fd 100644 --- a/src/payloads/edit_message_live_location_inline.rs +++ b/src/payloads/edit_message_live_location_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -24,8 +24,8 @@ impl_payload! { optional { /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/edit_message_media.rs b/src/payloads/edit_message_media.rs index f85aa27f..ebf88244 100644 --- a/src/payloads/edit_message_media.rs +++ b/src/payloads/edit_message_media.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_media_inline.rs b/src/payloads/edit_message_media_inline.rs index 1a3b5d28..f1c2a823 100644 --- a/src/payloads/edit_message_media_inline.rs +++ b/src/payloads/edit_message_media_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_reply_markup.rs b/src/payloads/edit_message_reply_markup.rs index cc7b2777..54f7344f 100644 --- a/src/payloads/edit_message_reply_markup.rs +++ b/src/payloads/edit_message_reply_markup.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_reply_markup_inline.rs b/src/payloads/edit_message_reply_markup_inline.rs index a34db1a6..ef3f0c9e 100644 --- a/src/payloads/edit_message_reply_markup_inline.rs +++ b/src/payloads/edit_message_reply_markup_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_text.rs b/src/payloads/edit_message_text.rs index fe475bce..f49aa53a 100644 --- a/src/payloads/edit_message_text.rs +++ b/src/payloads/edit_message_text.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_text_inline.rs b/src/payloads/edit_message_text_inline.rs index 758de475..4ecdbfc3 100644 --- a/src/payloads/edit_message_text_inline.rs +++ b/src/payloads/edit_message_text_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/export_chat_invite_link.rs b/src/payloads/export_chat_invite_link.rs index 44b5ec3d..86b861c2 100644 --- a/src/payloads/export_chat_invite_link.rs +++ b/src/payloads/export_chat_invite_link.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/forward_message.rs b/src/payloads/forward_message.rs index ce11ac85..2afb7845 100644 --- a/src/payloads/forward_message.rs +++ b/src/payloads/forward_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat.rs b/src/payloads/get_chat.rs index c110722c..c04619d0 100644 --- a/src/payloads/get_chat.rs +++ b/src/payloads/get_chat.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat_administrators.rs b/src/payloads/get_chat_administrators.rs index 10cf24e4..63af85dd 100644 --- a/src/payloads/get_chat_administrators.rs +++ b/src/payloads/get_chat_administrators.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat_member.rs b/src/payloads/get_chat_member.rs index f5e80f55..b29f05ed 100644 --- a/src/payloads/get_chat_member.rs +++ b/src/payloads/get_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat_members_count.rs b/src/payloads/get_chat_members_count.rs index 0fd2c05e..dd48952b 100644 --- a/src/payloads/get_chat_members_count.rs +++ b/src/payloads/get_chat_members_count.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_file.rs b/src/payloads/get_file.rs index 47d9225c..b5e29fd6 100644 --- a/src/payloads/get_file.rs +++ b/src/payloads/get_file.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_game_high_scores.rs b/src/payloads/get_game_high_scores.rs index 2678043a..565a607d 100644 --- a/src/payloads/get_game_high_scores.rs +++ b/src/payloads/get_game_high_scores.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_me.rs b/src/payloads/get_me.rs index 76e97ab7..8794d5a6 100644 --- a/src/payloads/get_me.rs +++ b/src/payloads/get_me.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_my_commands.rs b/src/payloads/get_my_commands.rs index bfa35ecb..8d75475a 100644 --- a/src/payloads/get_my_commands.rs +++ b/src/payloads/get_my_commands.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_sticker_set.rs b/src/payloads/get_sticker_set.rs index 35466950..a1b74857 100644 --- a/src/payloads/get_sticker_set.rs +++ b/src/payloads/get_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_updates.rs b/src/payloads/get_updates.rs index 3dba9e34..12965cc2 100644 --- a/src/payloads/get_updates.rs +++ b/src/payloads/get_updates.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_user_profile_photos.rs b/src/payloads/get_user_profile_photos.rs index 9ae52286..02cc174e 100644 --- a/src/payloads/get_user_profile_photos.rs +++ b/src/payloads/get_user_profile_photos.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_webhook_info.rs b/src/payloads/get_webhook_info.rs index 846c907a..daf89088 100644 --- a/src/payloads/get_webhook_info.rs +++ b/src/payloads/get_webhook_info.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -8,8 +8,8 @@ use crate::types::WebhookInfo; impl_payload! { /// Use this method to get current webhook status. Requires no parameters. On success, returns a [`WebhookInfo`] object. If the bot is using [`GetUpdates`], will return an object with the _url_ field empty. /// - /// [`GetUpdates`]: crate::payloads::GetUpdates /// [`WebhookInfo`]: crate::types::WebhookInfo + /// [`GetUpdates`]: crate::payloads::GetUpdates #[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Serialize)] pub GetWebhookInfo (GetWebhookInfoSetters) => WebhookInfo { diff --git a/src/payloads/kick_chat_member.rs b/src/payloads/kick_chat_member.rs index 81f5014c..a51d6c6c 100644 --- a/src/payloads/kick_chat_member.rs +++ b/src/payloads/kick_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/leave_chat.rs b/src/payloads/leave_chat.rs index 27af51a1..ad7b1e84 100644 --- a/src/payloads/leave_chat.rs +++ b/src/payloads/leave_chat.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/pin_chat_message.rs b/src/payloads/pin_chat_message.rs index b3d31f1b..831d7fc6 100644 --- a/src/payloads/pin_chat_message.rs +++ b/src/payloads/pin_chat_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/promote_chat_member.rs b/src/payloads/promote_chat_member.rs index 6d259b4a..9d4bc68c 100644 --- a/src/payloads/promote_chat_member.rs +++ b/src/payloads/promote_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/restrict_chat_member.rs b/src/payloads/restrict_chat_member.rs index a168f493..10a1d685 100644 --- a/src/payloads/restrict_chat_member.rs +++ b/src/payloads/restrict_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_animation.rs b/src/payloads/send_animation.rs index 364f821e..6e69e562 100644 --- a/src/payloads/send_animation.rs +++ b/src/payloads/send_animation.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_audio.rs b/src/payloads/send_audio.rs index 87cb3de6..9ae5a396 100644 --- a/src/payloads/send_audio.rs +++ b/src/payloads/send_audio.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_chat_action.rs b/src/payloads/send_chat_action.rs index d4e4b859..90cf1661 100644 --- a/src/payloads/send_chat_action.rs +++ b/src/payloads/send_chat_action.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -20,13 +20,13 @@ impl_payload! { pub chat_id: ChatId [into], /// Type of action to broadcast. Choose one, depending on what the user is about to receive: typing for [text messages], upload_photo for [photos], record_video or upload_video for [videos], record_audio or upload_audio for [audio files], upload_document for [general files], find_location for [location data], record_video_note or upload_video_note for [video notes]. /// - /// [video notes]: crate::payloads::SendVideoNote - /// [audio files]: crate::payloads::SendAudio - /// [general files]: crate::payloads::SendDocument - /// [location data]: crate::payloads::SendLocation /// [text messages]: crate::payloads::SendMessage /// [photos]: crate::payloads::SendPhoto /// [videos]: crate::payloads::SendVideo + /// [audio files]: crate::payloads::SendAudio + /// [general files]: crate::payloads::SendDocument + /// [location data]: crate::payloads::SendLocation + /// [video notes]: crate::payloads::SendVideoNote pub action: ChatAction, } } diff --git a/src/payloads/send_contact.rs b/src/payloads/send_contact.rs index 2f617848..e97d6148 100644 --- a/src/payloads/send_contact.rs +++ b/src/payloads/send_contact.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_dice.rs b/src/payloads/send_dice.rs index a7400d23..759a6146 100644 --- a/src/payloads/send_dice.rs +++ b/src/payloads/send_dice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -14,10 +14,10 @@ impl_payload! { required { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) pub chat_id: ChatId [into], - /// Emoji on which the dice throw animation is based. Currently, must be one of “🎲”, â€œđŸŽ¯â€, or “🏀”. Dice can have values 1-6 for “🎲” and â€œđŸŽ¯â€, and values 1-5 for “🏀”. Defaults to “🎲” - pub emoji: DiceEmoji, } optional { + /// Emoji on which the dice throw animation is based. Currently, must be one of “🎲”, â€œđŸŽ¯â€, or “🏀”. Dice can have values 1-6 for “🎲” and â€œđŸŽ¯â€, and values 1-5 for “🏀”. Defaults to “🎲” + pub emoji: DiceEmoji, /// Sends the message [silently]. Users will receive a notification with no sound. /// /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages @@ -26,8 +26,8 @@ impl_payload! { pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_document.rs b/src/payloads/send_document.rs index 8b9e9fbd..de5854df 100644 --- a/src/payloads/send_document.rs +++ b/src/payloads/send_document.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -38,8 +38,8 @@ impl_payload! { pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_game.rs b/src/payloads/send_game.rs index 354573d9..b16536d9 100644 --- a/src/payloads/send_game.rs +++ b/src/payloads/send_game.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_invoice.rs b/src/payloads/send_invoice.rs index 8941bf16..e6b6dd06 100644 --- a/src/payloads/send_invoice.rs +++ b/src/payloads/send_invoice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_location.rs b/src/payloads/send_location.rs index f9a213e1..712072bb 100644 --- a/src/payloads/send_location.rs +++ b/src/payloads/send_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -32,8 +32,8 @@ impl_payload! { pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_media_group.rs b/src/payloads/send_media_group.rs index 3cdf6a1d..2241744a 100644 --- a/src/payloads/send_media_group.rs +++ b/src/payloads/send_media_group.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_message.rs b/src/payloads/send_message.rs index 9157d52f..e8757285 100644 --- a/src/payloads/send_message.rs +++ b/src/payloads/send_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -32,8 +32,8 @@ impl_payload! { pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_photo.rs b/src/payloads/send_photo.rs index 4cc847bf..b1ea5ae4 100644 --- a/src/payloads/send_photo.rs +++ b/src/payloads/send_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_poll.rs b/src/payloads/send_poll.rs index 5a83b5cc..870c8c0b 100644 --- a/src/payloads/send_poll.rs +++ b/src/payloads/send_poll.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_sticker.rs b/src/payloads/send_sticker.rs index 234b2951..f2d7254e 100644 --- a/src/payloads/send_sticker.rs +++ b/src/payloads/send_sticker.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_venue.rs b/src/payloads/send_venue.rs index 20ba4eb0..7d47446c 100644 --- a/src/payloads/send_venue.rs +++ b/src/payloads/send_venue.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_video.rs b/src/payloads/send_video.rs index e009fd0c..db789a09 100644 --- a/src/payloads/send_video.rs +++ b/src/payloads/send_video.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -47,8 +47,8 @@ impl_payload! { pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_video_note.rs b/src/payloads/send_video_note.rs index 8aecd4fb..c53c00bc 100644 --- a/src/payloads/send_video_note.rs +++ b/src/payloads/send_video_note.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -8,8 +8,8 @@ use crate::types::{ChatId, InputFile, Message, ReplyMarkup}; impl_payload! { /// 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. On success, the sent [`Message`] is returned. /// - /// [`Message`]: crate::types::Message /// [v.4.0]: https://core.telegram.org/bots/api#document + /// [`Message`]: crate::types::Message #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] pub SendVideoNote (SendVideoNoteSetters) => Message { required { @@ -37,8 +37,8 @@ impl_payload! { pub reply_to_message_id: i32, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/send_voice.rs b/src/payloads/send_voice.rs index d30e5b36..77605534 100644 --- a/src/payloads/send_voice.rs +++ b/src/payloads/send_voice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -8,8 +8,8 @@ use crate::types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}; impl_payload! { /// Use this method to send audio files, if you want Telegram clients to display the file as a playable voice message. For this to work, your audio must be in an .OGG file encoded with OPUS (other formats may be sent as [`Audio`] or [`Document`]). On success, the sent [`Message`] is returned. Bots can currently send voice messages of up to 50 MB in size, this limit may be changed in the future. /// - /// [`Audio`]: crate::types::Audio /// [`Document`]: crate::types::Document + /// [`Audio`]: crate::types::Audio /// [`Message`]: crate::types::Message #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] pub SendVoice (SendVoiceSetters) => Message { diff --git a/src/payloads/set_chat_administrator_custom_title.rs b/src/payloads/set_chat_administrator_custom_title.rs index 31b63c5e..8eb39719 100644 --- a/src/payloads/set_chat_administrator_custom_title.rs +++ b/src/payloads/set_chat_administrator_custom_title.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_description.rs b/src/payloads/set_chat_description.rs index 8abbe59a..9e0650f7 100644 --- a/src/payloads/set_chat_description.rs +++ b/src/payloads/set_chat_description.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_permissions.rs b/src/payloads/set_chat_permissions.rs index 196b320e..8aa73f8c 100644 --- a/src/payloads/set_chat_permissions.rs +++ b/src/payloads/set_chat_permissions.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_photo.rs b/src/payloads/set_chat_photo.rs index 48b518cf..7578ae27 100644 --- a/src/payloads/set_chat_photo.rs +++ b/src/payloads/set_chat_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_sticker_set.rs b/src/payloads/set_chat_sticker_set.rs index e28e569f..353f12f3 100644 --- a/src/payloads/set_chat_sticker_set.rs +++ b/src/payloads/set_chat_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_title.rs b/src/payloads/set_chat_title.rs index 1ccc4b9b..06c77581 100644 --- a/src/payloads/set_chat_title.rs +++ b/src/payloads/set_chat_title.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_game_score.rs b/src/payloads/set_game_score.rs index 8334f64c..75a32821 100644 --- a/src/payloads/set_game_score.rs +++ b/src/payloads/set_game_score.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_game_score_inline.rs b/src/payloads/set_game_score_inline.rs index 8b53accb..4adbc8ac 100644 --- a/src/payloads/set_game_score_inline.rs +++ b/src/payloads/set_game_score_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_my_commands.rs b/src/payloads/set_my_commands.rs index 5cbc46cf..854fa351 100644 --- a/src/payloads/set_my_commands.rs +++ b/src/payloads/set_my_commands.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_passport_data_errors.rs b/src/payloads/set_passport_data_errors.rs index ebd3ad3a..021ce4b9 100644 --- a/src/payloads/set_passport_data_errors.rs +++ b/src/payloads/set_passport_data_errors.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_sticker_position_in_set.rs b/src/payloads/set_sticker_position_in_set.rs index 8b131b2a..30abb8a1 100644 --- a/src/payloads/set_sticker_position_in_set.rs +++ b/src/payloads/set_sticker_position_in_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_sticker_set_thumb.rs b/src/payloads/set_sticker_set_thumb.rs index 7b142106..62e7af35 100644 --- a/src/payloads/set_sticker_set_thumb.rs +++ b/src/payloads/set_sticker_set_thumb.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_webhook.rs b/src/payloads/set_webhook.rs index c73b843f..65ee07e1 100644 --- a/src/payloads/set_webhook.rs +++ b/src/payloads/set_webhook.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/stop_message_live_location.rs b/src/payloads/stop_message_live_location.rs index fdaa08e5..e93caada 100644 --- a/src/payloads/stop_message_live_location.rs +++ b/src/payloads/stop_message_live_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -10,8 +10,8 @@ impl_payload! { /// /// See also: [`StopMessageLiveLocationInline`](crate::payloads::StopMessageLiveLocationInline) /// - /// [`StopMessageLiveLocation`]: crate::payloads::StopMessageLiveLocation /// [`Message`]: crate::types::Message + /// [`StopMessageLiveLocation`]: crate::payloads::StopMessageLiveLocation #[derive(Debug, PartialEq, Clone, Serialize)] pub StopMessageLiveLocation (StopMessageLiveLocationSetters) => Message { required { diff --git a/src/payloads/stop_message_live_location_inline.rs b/src/payloads/stop_message_live_location_inline.rs index b9e18e98..b51a3572 100644 --- a/src/payloads/stop_message_live_location_inline.rs +++ b/src/payloads/stop_message_live_location_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -24,8 +24,8 @@ impl_payload! { optional { /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating + /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards pub reply_markup: ReplyMarkup [into], } } diff --git a/src/payloads/stop_poll.rs b/src/payloads/stop_poll.rs index d1cf267f..1d50c4bf 100644 --- a/src/payloads/stop_poll.rs +++ b/src/payloads/stop_poll.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/unban_chat_member.rs b/src/payloads/unban_chat_member.rs index 76d73e4e..05c46665 100644 --- a/src/payloads/unban_chat_member.rs +++ b/src/payloads/unban_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/unpin_chat_message.rs b/src/payloads/unpin_chat_message.rs index bfc16094..d01945e2 100644 --- a/src/payloads/unpin_chat_message.rs +++ b/src/payloads/unpin_chat_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/upload_sticker_file.rs b/src/payloads/upload_sticker_file.rs index 82957d99..c303f05b 100644 --- a/src/payloads/upload_sticker_file.rs +++ b/src/payloads/upload_sticker_file.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (9a82143). +// This file is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/requests/requester.rs b/src/requests/requester.rs index 09ff0641..6328e396 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -5,9 +5,9 @@ use crate::{ payloads::{GetMe, SendMessage, *}, requests::Request, types::{ - AllowedUpdate, BotCommand, ChatAction, ChatId, ChatPermissions, DiceEmoji, - InlineQueryResult, InputFile, InputMedia, InputSticker, LabeledPrice, PassportElementError, - PollType, TargetMessage, + AllowedUpdate, BotCommand, ChatAction, ChatId, ChatPermissions, InlineQueryResult, + InputFile, InputMedia, InputSticker, LabeledPrice, PassportElementError, PollType, + TargetMessage, }, }; @@ -20,7 +20,7 @@ use crate::{ pub trait Requester { type Err: std::error::Error + Send; - // This block is auto generated by `cg` (de3765b). + // This block is auto generated by `cg` (be02d84). // **DO NOT EDIT THIS BLOCK**, // edit `cg` instead. @@ -240,7 +240,7 @@ pub trait Requester { type SendDice: Request; /// For Telegram documentation see [`SendDice`]. - fn send_dice(&self, chat_id: C, emoji: DiceEmoji) -> Self::SendDice + fn send_dice(&self, chat_id: C) -> Self::SendDice where C: Into; From 2ddbaa49ea3a8eef246b3780797c2c6e4e38cf05 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 8 Jan 2021 22:13:12 +0300 Subject: [PATCH 185/755] Add forward-to-deref Requester impls This commit adds ```rust impl Requester for D { ... } ``` where `D<_>` is `&_`, `&mut _`, `Box<_>`, `Arc<_>` and `Rc<_>`. --- src/requests/requester.rs | 85 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/src/requests/requester.rs b/src/requests/requester.rs index 6328e396..0de68ad5 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -715,3 +715,88 @@ pub trait Requester { where T: Into; } + +macro_rules! fty { + ($T:ident) => { + B::$T + }; +} + +macro_rules! fwd_deref { + ($m:ident $this:ident ($($arg:ident : $T:ty),*)) => { + core::ops::Deref::deref($this).$m($($arg),*) + }; +} + +macro_rules! forward_all { + () => { + requester_forward! { + get_me, get_updates, set_webhook, delete_webhook, get_webhook_info, + forward_message, send_message, send_photo, send_audio, send_document, send_video, + send_animation, send_voice, send_video_note, send_media_group, send_location, + edit_message_live_location, edit_message_live_location_inline, + stop_message_live_location, stop_message_live_location_inline, send_venue, + send_contact, send_poll, send_dice, send_chat_action, get_user_profile_photos, + get_file, kick_chat_member, unban_chat_member, restrict_chat_member, + promote_chat_member, set_chat_administrator_custom_title, set_chat_permissions, + export_chat_invite_link, set_chat_photo, delete_chat_photo, set_chat_title, + set_chat_description, pin_chat_message, unpin_chat_message, leave_chat, + get_chat, get_chat_administrators, get_chat_members_count,get_chat_member, + set_chat_sticker_set, delete_chat_sticker_set, answer_callback_query, + set_my_commands, get_my_commands, answer_inline_query, edit_message_text, + edit_message_text_inline, edit_message_caption, edit_message_caption_inline, + edit_message_media, edit_message_media_inline, edit_message_reply_markup, + edit_message_reply_markup_inline, stop_poll, delete_message, send_sticker, + get_sticker_set, upload_sticker_file, create_new_sticker_set, + add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, + set_sticker_set_thumb, send_invoice, answer_shipping_query, + answer_pre_checkout_query, set_passport_data_errors, send_game, + set_game_score, set_game_score_inline, get_game_high_scores => fwd_deref, fty + } + }; +} + +impl Requester for &B +where + B: Requester, +{ + type Err = B::Err; + + forward_all! {} +} + +impl Requester for &mut B +where + B: Requester, +{ + type Err = B::Err; + + forward_all! {} +} + +impl Requester for Box +where + B: Requester, +{ + type Err = B::Err; + + forward_all! {} +} + +impl Requester for std::sync::Arc +where + B: Requester, +{ + type Err = B::Err; + + forward_all! {} +} + +impl Requester for std::rc::Rc +where + B: Requester, +{ + type Err = B::Err; + + forward_all! {} +} From 555bfc93d5a1d788714d6dd0915b5ecf1951f503 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 13 Jan 2021 15:59:51 +0300 Subject: [PATCH 186/755] Refactor mime type handling - Remove `MimeWrapper` from public api - Use `mime::Mime` in public api instead --- src/types/animation.rs | 10 +- src/types/audio.rs | 10 +- src/types/document.rs | 8 +- src/types/inline_query_result_document.rs | 8 +- src/types/inline_query_result_video.rs | 10 +- src/types/non_telegram_types/mime.rs | 100 +++++++++++++++++++ src/types/non_telegram_types/mime_wrapper.rs | 44 -------- src/types/non_telegram_types/mod.rs | 4 +- src/types/video.rs | 8 +- src/types/voice.rs | 7 +- 10 files changed, 139 insertions(+), 70 deletions(-) create mode 100644 src/types/non_telegram_types/mime.rs delete mode 100644 src/types/non_telegram_types/mime_wrapper.rs diff --git a/src/types/animation.rs b/src/types/animation.rs index 9cf1b0e5..ae0c9de5 100644 --- a/src/types/animation.rs +++ b/src/types/animation.rs @@ -1,6 +1,7 @@ +use mime::Mime; use serde::{Deserialize, Serialize}; -use crate::types::{MimeWrapper, PhotoSize}; +use crate::types::PhotoSize; /// This object represents an animation file (GIF or H.264/MPEG-4 AVC video /// without sound). @@ -33,7 +34,8 @@ pub struct Animation { pub file_name: Option, /// A MIME type of the file as defined by a sender. - pub mime_type: Option, + #[serde(with = "crate::types::non_telegram_types::mime::opt_deser")] + pub mime_type: Option, /// A size of a file. pub file_size: Option, @@ -108,7 +110,7 @@ impl Animation { self } - pub fn mime_type(mut self, val: MimeWrapper) -> Self { + pub fn mime_type(mut self, val: Mime) -> Self { self.mime_type = Some(val); self } @@ -155,7 +157,7 @@ mod tests { file_size: Some(3452), }), file_name: Some("some".to_string()), - mime_type: Some(MimeWrapper("video/gif".parse().unwrap())), + mime_type: Some("video/gif".parse().unwrap()), file_size: Some(6500), }; let actual = serde_json::from_str::(json).unwrap(); diff --git a/src/types/audio.rs b/src/types/audio.rs index b9d6279d..23225eba 100644 --- a/src/types/audio.rs +++ b/src/types/audio.rs @@ -1,6 +1,7 @@ +use mime::Mime; use serde::{Deserialize, Serialize}; -use crate::types::{MimeWrapper, PhotoSize}; +use crate::types::PhotoSize; /// This object represents an audio file to be treated as music by the Telegram /// clients. @@ -27,7 +28,8 @@ pub struct Audio { pub title: Option, /// A MIME type of the file as defined by a sender. - pub mime_type: Option, + #[serde(with = "crate::types::non_telegram_types::mime::opt_deser")] + pub mime_type: Option, /// A size of a file. pub file_size: Option, @@ -91,7 +93,7 @@ impl Audio { self } - pub fn mime_type(mut self, val: MimeWrapper) -> Self { + pub fn mime_type(mut self, val: Mime) -> Self { self.mime_type = Some(val); self } @@ -135,7 +137,7 @@ mod tests { duration: 60, performer: Some("Performer".to_string()), title: Some("Title".to_string()), - mime_type: Some(serde_json::from_str("\"application/zip\"").unwrap()), + mime_type: Some("application/zip".parse().unwrap()), file_size: Some(123_456), thumb: Some(PhotoSize { file_id: "id".to_string(), diff --git a/src/types/document.rs b/src/types/document.rs index aa60c2db..75e28918 100644 --- a/src/types/document.rs +++ b/src/types/document.rs @@ -1,6 +1,7 @@ +use mime::Mime; use serde::{Deserialize, Serialize}; -use crate::types::{MimeWrapper, PhotoSize}; +use crate::types::PhotoSize; /// This object represents a general file (as opposed to [photos], [voice /// messages] and [audio files]). @@ -28,7 +29,8 @@ pub struct Document { pub file_name: Option, /// A MIME type of the file as defined by a sender. - pub mime_type: Option, + #[serde(with = "crate::types::non_telegram_types::mime::opt_deser")] + pub mime_type: Option, /// A size of a file. pub file_size: Option, @@ -79,7 +81,7 @@ impl Document { self } - pub fn mime_type(mut self, val: MimeWrapper) -> Self { + pub fn mime_type(mut self, val: Mime) -> Self { self.mime_type = Some(val); self } diff --git a/src/types/inline_query_result_document.rs b/src/types/inline_query_result_document.rs index 7cf0a5f2..780e2cb7 100644 --- a/src/types/inline_query_result_document.rs +++ b/src/types/inline_query_result_document.rs @@ -1,6 +1,7 @@ +use mime::Mime; use serde::{Deserialize, Serialize}; -use crate::types::{InlineKeyboardMarkup, InputMessageContent, MimeWrapper, ParseMode}; +use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// Represents a link to a file. /// @@ -35,7 +36,8 @@ pub struct InlineQueryResultDocument { /// Mime type of the content of the file, either `application/pdf` or /// `application/zip`. - pub mime_type: MimeWrapper, + #[serde(with = "crate::types::non_telegram_types::mime::deser")] + pub mime_type: Mime, /// Short description of the result. pub description: Option, @@ -94,7 +96,7 @@ impl InlineQueryResultDocument { self } - pub fn mime_type(mut self, val: MimeWrapper) -> Self { + pub fn mime_type(mut self, val: Mime) -> Self { self.mime_type = val; self } diff --git a/src/types/inline_query_result_video.rs b/src/types/inline_query_result_video.rs index 7d190473..a9d9030f 100644 --- a/src/types/inline_query_result_video.rs +++ b/src/types/inline_query_result_video.rs @@ -1,6 +1,7 @@ +use mime::Mime; use serde::{Deserialize, Serialize}; -use crate::types::{InlineKeyboardMarkup, InputMessageContent, MimeWrapper, ParseMode}; +use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// Represents a link to a page containing an embedded video player or a video /// file. @@ -20,7 +21,8 @@ pub struct InlineQueryResultVideo { pub video_url: String, /// Mime type of the content of video url, `text/html` or `video/mp4`. - pub mime_type: MimeWrapper, + #[serde(with = "crate::types::non_telegram_types::mime::deser")] + pub mime_type: Mime, /// URL of the thumbnail (jpeg only) for the video. pub thumb_url: String, @@ -69,7 +71,7 @@ impl InlineQueryResultVideo { pub fn new( id: S1, video_url: S2, - mime_type: MimeWrapper, + mime_type: Mime, thumb_url: S3, title: S4, ) -> Self @@ -112,7 +114,7 @@ impl InlineQueryResultVideo { self } - pub fn mime_type(mut self, val: MimeWrapper) -> Self { + pub fn mime_type(mut self, val: Mime) -> Self { self.mime_type = val; self } diff --git a/src/types/non_telegram_types/mime.rs b/src/types/non_telegram_types/mime.rs new file mode 100644 index 00000000..c5655cc7 --- /dev/null +++ b/src/types/non_telegram_types/mime.rs @@ -0,0 +1,100 @@ +use std::fmt; + +use mime::Mime; +use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; + +pub(crate) mod deser { + use mime::Mime; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + use super::{MimeDe, MimeSer}; + + pub(crate) fn serialize( + this: &Mime, + serializer: S, + ) -> Result<::Ok, ::Error> + where + S: Serializer, + { + MimeSer(this).serialize(serializer) + } + + pub(crate) fn deserialize<'de, D>( + deserializer: D, + ) -> Result>::Error> + where + D: Deserializer<'de>, + { + MimeDe::deserialize(deserializer).map(|MimeDe(m)| m) + } +} + +pub(crate) mod opt_deser { + use mime::Mime; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + use super::{MimeDe, MimeSer}; + + pub(crate) fn serialize( + this: &Option, + serializer: S, + ) -> Result<::Ok, ::Error> + where + S: Serializer, + { + this.as_ref().map(MimeSer).serialize(serializer) + } + + pub(crate) fn deserialize<'de, D>( + deserializer: D, + ) -> Result, >::Error> + where + D: Deserializer<'de>, + { + Option::::deserialize(deserializer).map(|opt| opt.map(|MimeDe(m)| m)) + } +} + +struct MimeSer<'a>(&'a Mime); + +impl Serialize for MimeSer<'_> { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + serializer.serialize_str(self.0.as_ref()) + } +} + +struct MimeVisitor; +impl<'a> Visitor<'a> for MimeVisitor { + type Value = MimeDe; + + fn expecting( + &self, + formatter: &mut fmt::Formatter<'_>, + ) -> Result<(), serde::export::fmt::Error> { + formatter.write_str("mime type") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + match v.parse::() { + Ok(mime_type) => Ok(MimeDe(mime_type)), + Err(e) => Err(E::custom(e)), + } + } +} + +struct MimeDe(Mime); + +impl<'de> Deserialize<'de> for MimeDe { + fn deserialize(deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(MimeVisitor) + } +} diff --git a/src/types/non_telegram_types/mime_wrapper.rs b/src/types/non_telegram_types/mime_wrapper.rs deleted file mode 100644 index 43326de6..00000000 --- a/src/types/non_telegram_types/mime_wrapper.rs +++ /dev/null @@ -1,44 +0,0 @@ -use derive_more::From; -use mime::Mime; -use serde::{de::Visitor, export::Formatter, Deserialize, Deserializer, Serialize, Serializer}; - -/// Serializable & deserializable `MIME` wrapper. -#[derive(Clone, Debug, Eq, Hash, PartialEq, From)] -pub struct MimeWrapper(pub Mime); - -impl Serialize for MimeWrapper { - fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> - where - S: Serializer, - { - serializer.serialize_str(self.0.as_ref()) - } -} - -struct MimeVisitor; -impl<'a> Visitor<'a> for MimeVisitor { - type Value = MimeWrapper; - - fn expecting(&self, formatter: &mut Formatter<'_>) -> Result<(), serde::export::fmt::Error> { - formatter.write_str("mime type") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - match v.parse::() { - Ok(mime_type) => Ok(MimeWrapper(mime_type)), - Err(e) => Err(E::custom(e)), - } - } -} - -impl<'de> Deserialize<'de> for MimeWrapper { - fn deserialize(deserializer: D) -> Result>::Error> - where - D: Deserializer<'de>, - { - deserializer.deserialize_str(MimeVisitor) - } -} diff --git a/src/types/non_telegram_types/mod.rs b/src/types/non_telegram_types/mod.rs index 6c8e1636..49360c4f 100644 --- a/src/types/non_telegram_types/mod.rs +++ b/src/types/non_telegram_types/mod.rs @@ -1,9 +1,9 @@ pub use country_code::*; pub use currency::*; -pub use mime_wrapper::*; pub use non_strict_vec::*; mod country_code; mod currency; -mod mime_wrapper; mod non_strict_vec; + +pub(crate) mod mime; diff --git a/src/types/video.rs b/src/types/video.rs index 98241c49..1db5737f 100644 --- a/src/types/video.rs +++ b/src/types/video.rs @@ -1,6 +1,7 @@ +use mime::Mime; use serde::{Deserialize, Serialize}; -use crate::types::{MimeWrapper, PhotoSize}; +use crate::types::PhotoSize; /// This object represents a video file. /// @@ -29,7 +30,8 @@ pub struct Video { pub thumb: Option, /// Mime type of a file as defined by sender. - pub mime_type: Option, + #[serde(with = "crate::types::non_telegram_types::mime::opt_deser")] + pub mime_type: Option, /// File size. pub file_size: Option, @@ -95,7 +97,7 @@ impl Video { self } - pub fn mime_type(mut self, val: MimeWrapper) -> Self { + pub fn mime_type(mut self, val: Mime) -> Self { self.mime_type = Some(val); self } diff --git a/src/types/voice.rs b/src/types/voice.rs index f60cc204..2fc7a5b9 100644 --- a/src/types/voice.rs +++ b/src/types/voice.rs @@ -1,4 +1,4 @@ -use crate::types::MimeWrapper; +use mime::Mime; use serde::{Deserialize, Serialize}; /// This object represents a voice note. @@ -19,7 +19,8 @@ pub struct Voice { pub duration: u32, /// MIME type of the file as defined by sender. - pub mime_type: Option, + #[serde(with = "crate::types::non_telegram_types::mime::opt_deser")] + pub mime_type: Option, /// File size. pub file_size: Option, @@ -61,7 +62,7 @@ impl Voice { self } - pub fn mime_type(mut self, val: MimeWrapper) -> Self { + pub fn mime_type(mut self, val: Mime) -> Self { self.mime_type = Some(val); self } From 785237985201e5445424032e2836b6b763d72290 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 13 Jan 2021 16:08:05 +0300 Subject: [PATCH 187/755] Make Currency and CountryCode ISO compatible and add docs --- src/types/non_telegram_types/country_code.rs | 250 +++++++++++++++++ src/types/non_telegram_types/currency.rs | 275 +++++++++++++++++++ 2 files changed, 525 insertions(+) diff --git a/src/types/non_telegram_types/country_code.rs b/src/types/non_telegram_types/country_code.rs index 4f7705ac..ddb4154e 100644 --- a/src/types/non_telegram_types/country_code.rs +++ b/src/types/non_telegram_types/country_code.rs @@ -1,254 +1,504 @@ use serde::{Deserialize, Serialize}; +/// ISO 3166-1 alpha-2 language code. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum CountryCode { + /// Andorra AD, + /// United Arab Emirates AE, + /// Afghanistan AF, + /// Antigua and Barbuda AG, + /// Anguilla AI, + /// Albania AL, + /// Armenia AM, + /// Angola AO, + /// Antarctica AQ, + /// Argentina AR, + /// American Samoa AS, + /// Austria AT, + /// Australia AU, + /// Aruba AW, + /// Åland Islands AX, + /// Azerbaijan AZ, + /// Bosnia and Herzegovina BA, + /// Barbados BB, + /// Bangladesh BD, + /// Belgium BE, + /// Burkina Faso BF, + /// Bulgaria BG, + /// Bahrain BH, + /// Burundi BI, + /// Benin BJ, + /// Saint BarthÊlemy BL, + /// Bermuda BM, + /// Brunei Darussalam BN, + /// Bolivia (Plurinational State of) BO, + /// Bonaire, Sint Eustatius and Saba BQ, + /// Brazil BR, + /// Bahamas BS, + /// Bhutan BT, + /// Bouvet Island BV, + /// Botswana BW, + /// Belarus BY, + /// Belize BZ, + /// Canada CA, + /// Cocos (Keeling) Islands CC, + /// Congo, Democratic Republic of the CD, + /// Central African Republic CF, + /// Congo CG, + /// Switzerland CH, + /// Côte d'Ivoire CI, + /// Cook Islands CK, + /// Chile CL, + /// Cameroon CM, + /// China CN, + /// Colombia CO, + /// Costa Rica CR, + /// Cuba CU, + /// Cabo Verde CV, + /// Curaçao CW, + /// Christmas Island CX, + /// Cyprus CY, + /// Czechia CZ, + /// Germany DE, + /// Djibouti DJ, + /// Denmark DK, + /// Dominica DM, + /// Dominican Republic DO, + /// Algeria DZ, + /// Ecuador EC, + /// Estonia EE, + /// Egypt EG, + /// Western Sahara EH, + /// Eritrea ER, + /// Spain ES, + /// Ethiopia ET, + /// Finland FI, + /// Fiji FJ, + /// Falkland Islands (Malvinas) FK, + /// Micronesia (Federated States of) FM, + /// Faroe Islands FO, + /// France FR, + /// Gabon GA, + /// United Kingdom of Great Britain and Northern Ireland GB, + /// Grenada GD, + /// Georgia GE, + /// French Guiana GF, + /// Guernsey GG, + /// Ghana GH, + /// Gibraltar GI, + /// Greenland GL, + /// Gambia GM, + /// Guinea GN, + /// Guadeloupe GP, + /// Equatorial Guinea GQ, + /// Greece GR, + /// South Georgia and the South Sandwich Islands GS, + /// Guatemala GT, + /// Guam GU, + /// Guinea-Bissau GW, + /// Guyana GY, + /// Hong Kong HK, + /// Heard Island and McDonald Islands HM, + /// Honduras HN, + /// Croatia HR, + /// Haiti HT, + /// Hungary HU, + /// Indonesia ID, + /// Ireland IE, + /// Israel IL, + /// Isle of Man IM, + /// India IN, + /// British Indian Ocean Territory IO, + /// Iraq IQ, + /// Iran (Islamic Republic of) IR, + /// Iceland IS, + /// Italy IT, + /// Jersey JE, + /// Jamaica JM, + /// Jordan JO, + /// Japan JP, + /// Kenya KE, + /// Kyrgyzstan KG, + /// Cambodia KH, + /// Kiribati KI, + /// Comoros KM, + /// Saint Kitts and Nevis KN, + /// Korea (Democratic People's Republic of) KP, + /// Korea, Republic of KR, + /// Kuwait KW, + /// Cayman Islands KY, + /// Kazakhstan KZ, + /// Lao People's Democratic Republic LA, + /// Lebanon LB, + /// Saint Lucia LC, + /// Liechtenstein LI, + /// Sri Lanka LK, + /// Liberia LR, + /// Lesotho LS, + /// Lithuania LT, + /// Luxembourg LU, + /// Latvia LV, + /// Libya LY, + /// Morocco MA, + /// Monaco MC, + /// Moldova, Republic of MD, + /// Montenegro ME, + /// Saint Martin (French part) MF, + /// Madagascar MG, + /// Marshall Islands MH, + /// North Macedonia MK, + /// Mali ML, + /// Myanmar MM, + /// Mongolia MN, + /// Macao MO, + /// Northern Mariana Islands MP, + /// Martinique MQ, + /// Mauritania MR, + /// Montserrat MS, + /// Malta MT, + /// Mauritius MU, + /// Maldives MV, + /// Malawi MW, + /// Mexico MX, + /// Malaysia MY, + /// Mozambique MZ, + /// Namibia NA, + /// New Caledonia NC, + /// Niger NE, + /// Norfolk Island NF, + /// Nigeria NG, + /// Nicaragua NI, + /// Netherlands NL, + /// Norway NO, + /// Nepal NP, + /// Nauru NR, + /// Niue NU, + /// New Zealand NZ, + /// Oman OM, + /// Panama PA, + /// Peru PE, + /// French Polynesia PF, + /// Papua New Guinea PG, + /// Philippines PH, + /// Pakistan PK, + /// Poland PL, + /// Saint Pierre and Miquelon PM, + /// Pitcairn PN, + /// Puerto Rico PR, + /// Palestine, State of PS, + /// Portugal PT, + /// Palau PW, + /// Paraguay PY, + /// Qatar QA, + /// RÊunion RE, + /// Romania RO, + /// Serbia RS, + /// Russian Federation RU, + /// Rwanda RW, + /// Saudi Arabia SA, + /// Solomon Islands SB, + /// Seychelles SC, + /// Sudan SD, + /// Sweden SE, + /// Singapore SG, + /// Saint Helena, Ascension and Tristan da Cunha SH, + /// Slovenia SI, + /// Svalbard and Jan Mayen SJ, + /// Slovakia SK, + /// Sierra Leone SL, + /// San Marino SM, + /// Senegal SN, + /// Somalia SO, + /// Suriname SR, + /// South Sudan SS, + /// Sao Tome and Principe ST, + /// El Salvador SV, + /// Sint Maarten (Dutch part) SX, + /// Syrian Arab Republic SY, + /// Eswatini SZ, + /// Turks and Caicos Islands TC, + /// Chad TD, + /// French Southern Territories TF, + /// Togo TG, + /// Thailand TH, + /// Tajikistan TJ, + /// Tokelau TK, + /// Timor-Leste TL, + /// Turkmenistan TM, + /// Tunisia TN, + /// Tonga TO, + /// Turkey TR, + /// Trinidad and Tobago TT, + /// Tuvalu TV, + /// Taiwan, Province of China TW, + /// Tanzania, United Republic of TZ, + /// Ukraine UA, + /// Uganda UG, + /// United States Minor Outlying Islands UM, + /// United States of America US, + /// Uruguay UY, + /// Uzbekistan UZ, + /// Holy See VA, + /// Saint Vincent and the Grenadines VC, + /// Venezuela (Bolivarian Republic of) VE, + /// Virgin Islands (British) VG, + /// Virgin Islands (U.S.) VI, + /// Viet Nam VN, + /// Vanuatu VU, + /// Wallis and Futuna WF, + /// Samoa WS, + /// Yemen YE, + /// Mayotte YT, + /// South Africa ZA, + /// Zambia ZM, + /// Zimbabwe ZW, } diff --git a/src/types/non_telegram_types/currency.rs b/src/types/non_telegram_types/currency.rs index 0cdebce6..dcdec729 100644 --- a/src/types/non_telegram_types/currency.rs +++ b/src/types/non_telegram_types/currency.rs @@ -1,89 +1,364 @@ use serde::{Deserialize, Serialize}; +/// ISO 4217 currency. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum Currency { + /// United Arab Emirates dirham AED, + /// Afghan afghani AFN, + /// Albanian lek ALL, + /// Armenian dram AMD, + /// Netherlands Antillean guilder + ANG, + /// Angolan kwanza + AOA, + /// Argentine peso ARS, + /// Australian dollar AUD, + /// Aruban florin + AWG, + /// Azerbaijani manat AZN, + /// Bosnia and Herzegovina convertible mark BAM, + /// Barbados dollar + BBD, + /// Bangladeshi taka BDT, + /// Bulgarian lev BGN, + /// Bahraini dinar + BHD, + /// Burundian franc + BIF, + /// Bermudian dollar + BMD, + /// Brunei dollar BND, + /// Boliviano BOB, + /// Bolivian Mvdol (funds code) + BOV, + /// Brazilian real BRL, + /// Bahamian dollar + BSD, + /// Bhutanese ngultrum + BTN, + /// Botswana pula + BWP, + /// Belarusian ruble + BYN, + /// Belize dollar + BZD, + /// Canadian dollar CAD, + /// Congolese franc + CDF, + /// WIR euro (complementary currency) + CHE, + /// Swiss franc CHF, + /// WIR franc (complementary currency) + CHW, + /// Unidad de Fomento (funds code) + CLF, + /// Chilean peso CLP, + /// Chinese yuan CNY, + /// Colombian peso COP, + /// Unidad de Valor Real (UVR) (funds code) + COU, + /// Costa Rican colon CRC, + /// Cuban convertible peso + CUC, + /// Cuban peso + CUP, + /// Cape Verdean escudo + CVE, + /// Czech koruna CZK, + /// Djiboutian franc + DJF, + /// Danish krone DKK, + /// Dominican peso DOP, + /// Algerian dinar DZD, + /// Egyptian pound EGP, + /// Eritrean nakfa + ERN, + /// Ethiopian birr + ETB, + /// Euro EUR, + /// Fiji dollar + FJD, + /// Falkland Islands pound + FKP, + /// Pound sterling GBP, + /// Georgian lari GEL, + /// Ghanaian cedi + GHS, + /// Gibraltar pound + GIP, + /// Gambian dalasi + GMD, + /// Guinean franc + GNF, + /// Guatemalan quetzal GTQ, + /// Guyanese dollar + GYD, + /// Hong Kong dollar HKD, + /// Honduran lempira HNL, + /// Croatian kuna HRK, + /// Haitian gourde + HTG, + /// Hungarian forint HUF, + /// Indonesian rupiah IDR, + /// Israeli new shekel ILS, + /// Indian rupee INR, + /// Iraqi dinar + IQD, + /// Iranian rial + IRR, + /// Icelandic krÃŗna ISK, + /// Jamaican dollar JMD, + /// Jordanian dinar + JOD, + /// Japanese yen JPY, + /// Kenyan shilling KES, + /// Kyrgyzstani som KGS, + /// Cambodian riel + KHR, + /// Comoro franc + KMF, + /// North Korean won + KPW, + /// South Korean won KRW, + /// Kuwaiti dinar + KWD, + /// Cayman Islands dollar + KYD, + /// Kazakhstani tenge KZT, + /// Lao kip + LAK, + /// Lebanese pound LBP, + /// Sri Lankan rupee LKR, + /// Liberian dollar + LRD, + /// Lesotho loti + LSL, + /// Libyan dinar + LYD, + /// Moroccan dirham MAD, + /// Moldovan leu MDL, + /// Malagasy ariary + MGA, + /// Macedonian denar + MKD, + /// Myanmar kyat + MMK, + /// Mongolian tÃļgrÃļg MNT, + /// Macanese pataca + MOP, + /// Mauritanian ouguiya + MRU, + /// Mauritian rupee MUR, + /// Maldivian rufiyaa MVR, + /// Malawian kwacha + MWK, + /// Mexican peso MXN, + /// Mexican Unidad de Inversion (UDI) (funds code) + MXV, + /// Malaysian ringgit MYR, + /// Mozambican metical MZN, + /// Namibian dollar + NAD, + /// Nigerian naira NGN, + /// Nicaraguan cÃŗrdoba NIO, + /// Norwegian krone NOK, + /// Nepalese rupee NPR, + /// New Zealand dollar NZD, + /// Omani rial + OMR, + /// Panamanian balboa PAB, + /// Peruvian sol PEN, + /// Papua New Guinean kina + PGK, + /// Philippine peso PHP, + /// Pakistani rupee PKR, + /// Polish złoty PLN, + /// Paraguayan guaraní PYG, + /// Qatari riyal QAR, + /// Romanian leu RON, + /// Serbian dinar RSD, + /// Russian ruble RUB, + /// Rwandan franc + RWF, + /// Saudi riyal SAR, + /// Solomon Islands dollar + SBD, + /// Seychelles rupee + SCR, + /// Sudanese pound + SDG, + /// Swedish krona/kronor SEK, + /// Singapore dollar SGD, + /// Saint Helena pound + SHP, + /// Sierra Leonean leone + SLL, + /// Somali shilling + SOS, + /// Surinamese dollar + SRD, + /// South Sudanese pound + SSP, + /// SÃŖo TomÊ and Príncipe dobra + STN, + /// Salvadoran colÃŗn + SVC, + /// Syrian pound + SYP, + /// Swazi lilangeni + SZL, + /// Thai baht THB, + /// Tajikistani somoni TJS, + /// Turkmenistan manat + TMT, + /// Tunisian dinar + TND, + /// Tongan paĘģanga + TOP, + /// Turkish lira TRY, + /// Trinidad and Tobago dollar TTD, + /// New Taiwan dollar TWD, + /// Tanzanian shilling TZS, + /// Ukrainian hryvnia UAH, + /// Ugandan shilling UGX, + /// United States dollar USD, + /// United States dollar (next day) (funds code) + USN, + /// Uruguay Peso en Unidades Indexadas (URUIURUI) (funds code) + UYI, + /// Uruguayan peso UYU, + /// Unidad previsional + UYW, + /// Uzbekistan som UZS, + /// Venezuelan bolívar soberano + VES, + /// Vietnamese đáģ“ng VND, + /// Vanuatu vatu + VUV, + /// Samoan tala + WST, + /// CFA franc BEAC + XAF, + /// Silver (one troy ounce) + XAG, + /// Gold (one troy ounce) + XAU, + /// European Composite Unit (EURCO) (bond market unit) + XBA, + /// European Monetary Unit (E.M.U.-6) (bond market unit) + XBB, + /// European Unit of Account 9 (E.U.A.-9) (bond market unit) + XBC, + /// European Unit of Account 17 (E.U.A.-17) (bond market unit) + XBD, + /// East Caribbean dollar + XCD, + /// Special drawing rights + XDR, + /// CFA franc BCEAO + XOF, + /// Palladium (one troy ounce) + XPD, + /// CFP franc (franc Pacifique) + XPF, + /// Platinum (one troy ounce) + XPT, + /// SUCRE + XSU, + /// Code reserved for testing + XTS, + /// ADB Unit of Account + XUA, + /// No currency + XXX, + /// Yemeni rial YER, + /// South African rand ZAR, + /// Zambian kwacha + ZMW, + /// Zimbabwean dollar + ZWL, } From e513cbb453155ed3a1460204dc58ba8d939a8aaa Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 13 Jan 2021 16:10:56 +0300 Subject: [PATCH 188/755] Add media (example gif & logo) --- media/example.gif | Bin 0 -> 110031 bytes media/logo.png | Bin 0 -> 52318 bytes media/logo.svg | 1907 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1907 insertions(+) create mode 100644 media/example.gif create mode 100644 media/logo.png create mode 100644 media/logo.svg diff --git a/media/example.gif b/media/example.gif new file mode 100644 index 0000000000000000000000000000000000000000..a0605aa6b5455e19e0281efe17329666394b4355 GIT binary patch literal 110031 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 Sh9NKNz#Q3Ds!#~P@V@||Ys6Uq literal 0 HcmV?d00001 diff --git a/media/logo.png b/media/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2155dd30fb8636c6c0c86549277e426aa48bdc9b GIT binary patch literal 52318 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

p)%MU^)-HM?16J^fj(Y4 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

LMRtME)i)B;Mx34TD5rIR$bjR@aOZ9v+$|y zP4SmZyOk!s$5X!3dMpbgE+J$#u_STM7_Dxe-x*&pgbhSt27Kh?!$S`rAw){YHGQ9C zdMOTnoV~yz+t+(B(4liDxnKQLmeYd@P}5$rq<&kLGWnsFqN|uPfZOgrL;W0lf6iS7 zQ3X;r+Cf#i2F2Iygh4i0x46~rZHemeqy>L=x`_hxN0xr_AIh|t)jR^qK(E{D{WVNQ zg?OnxAK%%RM0Dq{F2KcU;w(-8Ua>N$AfKZlT$F}mUCEcLgD#iO+jKmuNPcz=?{j{ zv%wsvM=x$vF~ktr4|R1GZuI$(uIHy<`L7kX#C3rD=$1kcpd$@>eq=`XzvU1Q$A0I* zpT1i${rd9Ul{f%@yS84f8~M+!aMvkbI05-g&*r~Nyh!k+0(t9erT4^(x zO)bA4+fDVbzbK$CsIUAW7?I3`_Ro1mRiaOdwSHE*?_;KL}bcRAy?%a1}bc!7d-6)wIl#4VuX3x4`I6FXT#6cYIL zJEj8fdtTBF90s+@OnNra5FmkLM;usP3V~fI9C=a+1mYNy_GyAg8P?;HGs~1$aJd*Q zE%p2>8c6UF(6j!|`H-IkZ;h-h^(I%ErJbS!u`T617lWNwiio~+1L%R$E4Q85tW}$Ddwl2On%ob_eq)B&USG%X zwrk*7zwPlM^bP5rIcm{>oN|>Vh5u9}PXd?5s4#r{N|n`!J<-_0VzY`UAPR>z13Skx z)LIp1T3|!XbN1=@BB(kwqS?=-cBqH%+$UlDv!iCI975|xM|edGF;EvSdENDlvG4Vz zJ4deJiiZ-OYM6~kC-zE=^T(iY5;OYO?X@cgW>PZ2kBKpupO3=}?&lO%8FoBV6VeVj zjGsdVnKwYYjznuXq~DPBp8h-`L`Zl^5DiJb9oK_T$7|y(f)0E{a`z1yO0&w1E z)r?_K1qGWm9?^kTESo~BQ6!1vtIsR|34snk506syUy9+yJ-5XP1aN}_qK6QiC*RU{ zSOcUss{M@b*EBUV1FH}!DtoshOPZAd9s6MHA6`0$mtD&*o+vp;^&ggQL-lLnD=vL< zUn+tWvL3?6{C-@f`jlsHNH~EJ@|8`Y{ zBwspHJqW*q+$AkLsz?&ofKlNkbpXzeNb;mkAhSH;=EHmCG9};rEzI^@K$UV&zDNwz zulch4;2uH60({r#;6_#D-o4l3JpM01voTxRLq7&^1e3K{0E7_IQVzPHXM9KW&sr%$ zFd&l5>32_%2Kev2T>4atjeEI1SKb?q`nVC%LSC{$`kXgo08QkQoreHBw7UV2IZRV3 z+I>$SBKl%Wcy`OmAai>;u{AsHqhX&jx(`M66;En}=W+8hj#*ArG(r5qQYtI}i{`>3 zHpE!mOFHGP<}B!_c#nzYLi>@_KO`;V4y0cAP1yOMN0%(aQGppP)U1HTU`;!);c3u< z7uX>pP#9Um4>987bA;d+wljfyjpp^f{C<0WgBIFvdFr0b%31m^Mwx@$(+}Z%P9Wdr zLUYtiopD}s`o=OoTj&NqGJ$2p1nk1aVw9rsC{vU@s6?P9oZwZhYGUoqwWG03Pagf_ zO#7ktJYgq^+h4oa+~(`&ay-8H)Xqi!6d0h^s`qVHP;S|zzf=ltPoEPTCy4BduxR||7s!WTTVC=^xfxZTd_%U&wAe%`3F`PT$?^aI zxd#;Hdd_%tgMoSm2VmwKu5$XnP%f}m@Js5^$2t!Xg9`G$xN!jst|fH^Tu$9Oz%hm7 zRK(D(407sH&C*MNcd#0| zYNi!jk!mpBC(XGlw<%%dr`J(e0T->6A_(npa8i22`^KJMEh~YO?I_UT`SwuFE~iWJ zc)ZlL>DJsq`c-o+@8&fhS}G>}+-Ln6s`^^$&L=r4u;lOkv%(^5UC1U>T(bMFKJ3veW^$4g>yw@wKVVnWAOyJ`DMWLs-O2pJpyXRE)Rf z{&`I~l198&}`jcj82lzRP$FYPF0s2}O+vTRLwLucyW+t~FEHa;=1+wJ_! z5Od&(jpGF>o`pp*#?J#){Y#&Zml2Dhw{u#T$nCNLcH;i|TQM5OIA)&sVI9Ci`JJ=gmCD!?WLseIOerB z>t3&Jy!tJaB&}kHcfTW&zWbU*{@p79#_*V&mar_;V)kfAKXLX4ocfHaA{>;`fr2#F z=Ig(u!_(8#d)MM7TP5H&g41x}c-2dwIIt5eyGjhZTiNs^v0G&tc@KR*VZWU0gNL4o zYn`LcUWC$Ia~ec=bUZ>iw)!6$Za@~o!;&)@298|zIH!nvXtsvx0>m}xwp8}X*bcU!QA9C*_W5@*?VOOkKQF(u!W`Os*oMlbR11K-c2AuUQ@G&$Xv?P-b2)BLL0JyKM(G!nqPku z08F(Ci;9k>BIB3YZq5k7T#Fb`XYWuv7(Z4&3#yGu8c*)`XUb53PI9oz6(K&^$m`LO@oIQi*G%Gu; zs@vZ4j*}9?fE!zvzujo2zfxzKIS}x&Z9p4&X%YJalvI)yvqLth@#^`$eD}T6#Pcs- z^<+}(+O>3SNDLiE@_joo#U)78)b4OvP|v6x^93CSeY2T76SDF(K3rm_6NDEi!# z=^SO}5h`Hc5)#k?tAqTKcteV~4PUC^P>O~ACkuAemBu| zU-M8d5H8phZGcj9ta%&|qqo|WWWho)fH5`ZM7CmYBX zb+JgWk?OB!Y^9FBBw5oXtumtMJ#~L}%`ULQ9m`H=gVWK1X}=Y6cgAYIPlLX1mH&xv zp1X&u66d)#+@;IbhRa2o$Ax4QUU@3Ln9x=F(mlke3aim z2qAGh7t#tK`^M>niB}+NO8yjnaf=x~cUuI7JSS|Y#+%}o%<8?|(wbu4d;y(Fv@Ef9M;}z8s2Z$Zm43c4-A_-k)($ob{xnTHI3at+xUxnt`g5`Fr%ps4JaCuM= zv^`Bh%fZOVSmrt|TsmO1f#*_4R=(o_OwXhHt;xmR>XAJ3!E#y}?py2-dakuZ6VmPc zJ1J~TYWrA{2L+VG8?GiOWws-x6}G=rUM#4AUdS4ci~$c@n=V#QF4f}UD#AQq5hgw9s7US$78g$B|)^oR}J$tH-;#y61= z@X`X%_vkd9)(ATdwmlR?#z+L5Dzc%KoPrpiF$;0Ju!LIA8C=Q zWohrb{5Hj*uVya$JB3SVmJx)8z)e?ei>MOSZqW6WC?^rGzYZiTecAYNHC+*@+oPN6 zb*`>eq9{2H&rdplAQwUv$B``^*pTrS3;0~_h5(l#(2P<^2ReKkBe>-bA4eVD z4^D`!_Lhab73-_vg2b*yQ!ljrJU-IR`5`^kOx7|!2eWp4^d#D7e6}-IhkFDx24=0+c$NTn_@OWS_j8qotaJ;#nAWwHMtQ>w~f`p1PwsVC{XiYDg zTXGdNZ;}x3dJR!=xiGCzapr4YpV%we^U@wZYkVP>r06zoEL%*SJN6EER4rn8Tx?mk zks3#u<@i@O8^DNDgni|~IBJABP=i5|U)chJtGLX=1Mi*Bt45^$vo0aH&j-RvL&v(c z9?36U+kjB>Nmhn=@{8>tGf=J)uRg9%!8joXYS_Z zHFoj-Jh(?hlo-G!IV?6kC8Hw$Pq}3u=BbGC;MMajVzI=d-{F5S@6qrWHPqq%${pgi zU%8>_>=mk21>tkdVS4pb5fb@!nwaSd@quOaft0ShuU(zlE(3hqL>{1J1RW#{&q{`R z*!U3wOO_o5wWmI_t##4uu*cvDOnbyahF`oL)Wwqard=UZJ8WZV+X?P$H;|o@wjdw{ z7+Wah#kfj*ySmQ#lY5Y|AyKD(`cnN38Bkhf(6DYn2hh;a;EMp@l8)sl$WKyw&NTk% zcHiKf826p0&AU1SceeS7!(RPRCM!jo7y@3Wu$HrT%36Q6Gv5wcjO!GqD2u>p$Qc9Andma8Y8#hA)jIBygYgsrsNMuVeG$K7ES2#4nsWEB;0m zfih8$0ChM;icc{VXrsvYTgbtKJ>+=Re77G$Zf^|zMm9Rc#6_f~6o$}3pT+!84l`@n zB|>u;0_ILRAk_~AXsfH3E-6Ba6Yj+5=7=Z%>6*x03m9gz%_fN1Z*7EcOJrPfBLPHF z66BN*2vf5TKlbCfE}=do??eHhjbVTEuAyTib?_JrCs92Bd#3gv?{<=rc!9W*o*EcoWo!z8 zk+L6wpYQzi5LD#1kUi7s;xnc8zHJ|M%|27xD!U;uISp5$LAJbnO6F2}z`kXue)gpM z9rN<+>C5qbuaQGy;0+>-yUQ?Z`p>W1yFxZMlIHUd{;?OJP`|)0MCuFP7Et&^36EbB z4JW#-iHQSjYi!jPbYBDJF0xyNA95dAB)YAW;tcyZxhse&cKCP`ul)H#q7oyR#^x}* z^IpblNxf9xXdkXfIL=h!oXh5r(*z$1hJB3)!d_gcUXR&x%$#bMXlGu2JpENXlk$2n zCqr-RJ;mcqiu!UB5d-xNg^Z;1HkG7WLE4EctS5lZtt@})#L3(SakI$fGU;tH<;IYZ zb;=qS6&1A)hF<$HKb!Gl*pvSNT>xF`;GaSmm}28tG2MWn1U|-T{gYPze$vkGm*Yo{ zj^#it5#A@HEaUKxao*MbVI5-F>975RZS0ijM~-U8jlSJF!__DwK@vWM>;2e(rrQ)M zS)ov>9|eOnXTM#Cm{Y<&8{1$-7^vAvp@=sQ@P~BSFd@o2HaF`=e1bu%x2zAQB8zQW zFc5!G2jSJr|H8NJzWzw;7+wn!4%;76qp$^~77UxE^H7`N_Zqce zip1j4d5Vm%Cm+P99xio-CppERG6&>m!PB>Ed9{WtG>TzQZc9gD0-2(h!8==X;wiCt ziEh;8?A){&d|z6@qlE`9Ir~|J-#F3iCN&a&aF@3}P7{3yGiowTdV3IR0Xw?~$qjD@ z^T^`H@Pyjej}}Y5@3wKLS^nBDFoFaceF3b|7r2x~;;p;| zykCaZz7)b+(IwDel4unz(*&>Oj4zcgTtLAI1Dw5f@dXoz9IpuZJHorh6S?qj{wAcE zP()Jit>h0^DryCF!{xvwbHC|Rd88<f@^juNu;mM)%Vb$j(<>szV zDpbX(1yeaMP}JirEcNP*Wb9<~W*6Ne(B}Mat(eVw)3)t|YV>O#M1|;E3GLba6Lek~ zXqWodC(_JZGvlqB!@Ky%diChs6C~*^gFzoVBbcao@KpMtscFttf?7e5#B*|AifD5-p?nYpseEM5P#0V z(P_u8W&~FSpJ(B18*RN;OV<_Yi>#13Y4qgv;7kaVI3OMePjnCGtz8Hw+5f%%uwY;X!AGEMQF$jU79SJ`gpc#^`NI@{g&7?wyXPBxv_IZVHzE!-@7{MGp!cuU zERNW#MA$r-PH`CLV!!E7-$fCau!oAdrfrvhWlVA(%|F`B1IjWQErinp@okXI@t032 zoojmNP_XMpu&bEIPEY#A6yEX@1Trpz!f~&uz_ENm0JQ5JfI&3N{St$E)3tVBOvJ|o z*6!cH6%|C`0ZT4@P#G23_o;o07f}R%QfRMx9evqgaaKw4pjVA@;(Q+~ z)ULYRlBV`XF!VOUp)Q^)*JABNhqF6DfoKdwQ?#EG}ml2L@HVy1k-<){dya1s^ zA78^WQUH2U8-O+R(ktk1k@ZfcS7$h=*90#(;n5rJmLe}>R;eBbX#2S#A*R;|xL=f* zU7&=^I#PwVBJsO0`15IAXT^5dq2KvT@nUkEPt*t{wGrlxL??ow>5G7! zEd)?G#6~Wf;&IgOU09k z5GFHHTpBr;cd}8%K+MWMRYjNlGfOz~$veIDecp$h3uOvp&3Q0P45-2V-y=MBbxp0-=K}<8-8ulBd$a>DLoxt%7?|DHFz&?Xk40FM z+2s5r8R!4_w6P&X;M?qgd~YBJef0+#8kR-Uq~@!Mhot%X*CpA2Oc>hM!mjZ9v7<7@ zbO14D%pkiSDl!a|b7P5lpqDxTH8XCTFf}#xcfT`d3m!Yb+WDh4=@MveQxhx5Gwdl; zk^I3{uiC23o3#S!by+!PF2s(FbfFcWgx8l7&*NnozNNop51bM`v*DHGAO0G;vd2SE zoP&488(Le1+{s@Js@4mcd@2$MJqs94R7*#%5Q!RM98b~NLHsG?M3;g0E}Mru!SG+4 zZZql8OUp7Fm9~$6N}vnhMhyD=|Kn!odX%Y(AObN|9Uui7%$RHCH2U)4lNTpO9hUPR zp8MoF^ix|c8025DKe&Ws2j*%nDi`_zf|AsCy`Kv(XOM%3GEmw@Iz9-ZhRps6IA1^< z@ZJ{C{7y7r4?bpoKFB4w8C1`K){VHi4Q(_got+EsJ(=r(CBj(4sT*KU%(E7xTstBx zhwICeDZtlteGv%zVTF<_T*Xo?!J;u(-d2}hWlE%Fyp{%gQg9Q+=l|l%7Xg|Z!*F%W zT>(HRSl?0n-Se%_R++gA?9VA~$Rug~thc)7c&Q*WX*9x*EaH%EW9ey9N`IW$v7IL4!+p1SpOmh3N z##5g}sVBQDGz-%CD%T}@LUwH$q_zHpltkgR)qYI*vZ2feYI1K_zw$GJ(pzC&*hfD3 zTuN}+e0t{{3yO#oDZT2709?Nmj9yHkiHOD534SA8&omy#AcOg)j==~wZ^~oINgr>k z&d&L{hy}dHd^bb+tn6_Rh@;?w>pPWxK!Ztc)R}Ef=NQ<#dqL9W!nOr_H1#@pPnBIJ zUDOr|a_M)g7y=S-jMyWmXSSJTU<^ymMDqvTI1#C<*NEMso07axs(*3x% zsX9d|F}5iNNWuSgpkY^dQ$cobK=G&Wv0f~M&JexiqYYCLH(p4hFFg47Z3VI)sy=gT zB}79OgP#7hbQASL;-_r8E7c(_=(Ye)z=Yr?>}BWrs}Z<9Olw{Mv+6snZ!X5;8O#dH z#`(aki_2Z?$Cue%f5VG0O;}9-b3hjUk>0nE%b*VJNlfI zI)08Ht@YmiZ8tnqX2-I=<$h%hL4ZL-19#|pwEE- zM3L#A%ZWewmH+JqdVUpxTf(xZGd^NIYyBxD3}}8_5;P8pT&i!p53kY`>BbC`=vytq zoRyvSR+lTP$&*|p3IC(?9le)ZSxq_GTF|kly)^hQ@MYGsOSM-E4Z{>-_rmhU`Ft3R zEMET@kFBQOP>65wSd&k9pY+Lp>+>g_5W_cMp&#V1EQ9otZjcL()W6x&vdol010OtJJg853pnCws`inxRZkXX znzcN-wXlt`L*JEw&KiFsFH-H_!%N1dLP4M0N;YJ;fNgzYo6J*zZ9MU-nphuiSJh>K zZ$}K!OZ|4NaONf|5~MZ0IzBnh2|i)K1!dyGNZj4sueOTjxnF@IfPK&|w~V8bo*gXz zPmlV}=O_PXMC*9-^;2#@hrV&(hD33SY`{9B8op2j@s6ta>C|z(PQKA?4V_2cx$UZQ zJ>&S2x*g2*DFn#JCkJo;&FKyBT=GK_0EOiL378WTYRTN;N6tg|azFpQ!ND)A^ zHUc8@BK(IDmX+S3EDcKmeFXDwDtenid%^W^v)$&dcwbWs0-b|@#;Ne)``v%Y=$@8o z8S7bJB)nrZy@k#x9G9HT;wZ)As~kZ#`}xxYig*IsuU>R2~pj$*{e5uX9=7U=yi9 z=f~KUwQt`}eQoX@1Zu_>n{#ad6wXjomOrBN|CEi)xnJKqbE>sO=@Q z#jnwd%yZA|OPWA>r%se(-qy~JlK&Wp5fzTr-OrBaRUtAOaw>V#&~i}piUI1 zKG2rd|6u?g=d{zb0Ybv(FXASjZnY3Z3!$*SFMyiFOemoNs>N6*s?en5!Q!j+_VEml zoy3xX*171pXM7d%j!z`cd2^6JG4qrPcK_nZaurw%3Y7BOKCc z4aRGKJF^f77#|+i72io1RZP3opnU-$!Uyq;iwyAf`zynMFRF&V+aJZ7%%@@VoFlcW3$LkBiy4yRqjY*1Wqo zeRqNnGqF;$KD0NG3jc2+ntu$g;#^zH&%})+&ACC1@KW*VziqTXBNPRv^>GZ^#oPfo zG}m*U#MHNL-FhYLLdY>*L7VvN(oI?%gM%?O-#>Rw3adF7oR+`*79Nc44|_l{;` zoMatO{_b54n4|^IOAnQ=SI_l%@@idWko@zQ^0yS)H4g~>1Z*j_HuoI;y6|EC^iJJ- z+{%UM?gY8(Q=(Qk0y&!-i_CZ!5hm8~YUJzLk=Kg8=x9G=UiG3JcSk?mzwnQ*f_{n^ zK~$a)2mqT^41>t$f4QkKb6_-9K%KjSKhR}FboJyrX)F8DYtr*d&eNe;C;QCLv+MaN zoG>G`M;T{3YpbrGr}-x9PL3ZpCohH@q>8*e9Y}=Vws_nZJ|>sIg<&;gRy{9|1quh_ znSaU5*LU>3`4@|@2+8x$qcxdEWhY;xa@C}ShPeo!73^R-JJ)M6KG1~=`!d$jB!b?5 z1Ala!>%;k{0xs97zMiiAX$~nAPCJtqL6?i%-4KAjp*gzotemN}?yG;mNMa{~p z08Le(Bk6}konY49Ih_#iYtcI+D#@i5L7tIu!Ut z5vR0~`inW4uROdO@^W)=&^QBLPoDQurkAwQ9~(7OEQ0L8w78k;We27U7VJES$9BN!qo7o7E54p-4}GbBqH0uaK-k(lbf zZ|*qXCT0@cPG~XwH8YsKBZDISb^5Kym94N`aGy))89*i31)w%>$FLxRoHFK}%EMra zkG0qzeY}BL!@C1}`%3-4wF5NPpyg&bZ>4x$QO>gFvWd-}Cwq8ODr+RSx=@~$>fs+z zUBbXRH37O-pQX$tctL8iYgP%g7*yw^X7QOlYUXM{Evp{P1&0UALoN1WT=twx{?&)4 z3FHE>x@5b39VGJWPRb)vBNYkq-JB-*=do`pj&ZXv)hmOX50B{`bug^cuDudVIcUxQ zvQXSgNiDy#a^Mmsr>>51chbx3hF<^Q<=Y>l^5P`_t!a5LSkxZf*rH1kh{^R8*NyEH zGTnH~?%`u-(D0Reh(EbLg&p^&lgB*Pny-7X$66+!&P(b+RDWd+N$X<`p;}wCdnAuQ z7g>PmSCI{4g1Tc;bhmzuOKvL6i~;Eb>3{J!5p&MUhHtnpVvL)wHpdiu65KMOw{UUa z{YNg1NAX9zdX{6Z2xhz)#b&1^6KPB*l=8KM_{c5n_RXf|rKb1e&=E8Ugk-IIo-Gue z&LRv93&TfSSyU>h-=2lEX$zCg{eiUDtAqgIRTrSHWbr$7T`CLdfzBC)fxm5CKfIR= ze=t4BOhP7=AGO%1Zy4}q8z~V5PHo-m%?eRGKoxHR&CoCH=^%9ac;qQ8EbJ}Xz=e_j zBK*yNcT(l)%xnmRX1V`CVs0_o?Mo3m%#a-U+F4G$JGmvi=3O4xF|Ay=f5xNEAFtW0 zA(Nr0@F#`(Z}-|IR!@BxjH8DZ6#$AefO!aick00f9{+0W0hzq6+No8G&EaW1G1_g2Sv2{m_~AWfAD7{$^2m$Y{{7v*k8HUY3<3uG zbTWRohm!1Y+pY{V%Hf6T3IY-4<2igp9Hft3VRFq|$g>x(5wKuaXjxnG)$c2VaYPbE z@qVxeZZRdF?vt`1^4G-k3T>x;pRM%UH%=+;Vrs6PR{kq>TWNhXNtz7#m3{6EerM~W zsY5*5?!OC3M7`XQu*X107=99$R_}g|1T|VPssY`jsFL!Vyf0tA+|XFy@ea=#CZix` z0&*s&3rG=jbFQPD(!}xf(&yF;w&6c`EXg~1?_vBzaCv#TPE?64JV!XHWg^S1<5a|L zB)6n^bhmWjhxCpn>Ba7gwv}fyfrqDemj!uVACty|B_1}a_;d3Neo+*3t=cA`XMTqLuwrq0B$R%YD3CG1INkgPaZp+-6ob2nITtASw zus2Vx6Tvw8vO|sx@+%FMW6D>rU%S5(7FfZ|L|rIp1UZT9HFF}OX_x`tvc8PP3-yz? zJ*l;QXhZnCCw;Orv+6qCl2-rux|34V$*dq^KUo&EO$c_S6l1{Qri1}S;w_wn*Xi8C zw$Jpo_Q{EeJX?K9bN1_31}fI7fen#!m#9OEPty0a1%)!h$>LBkPYtRFr&3F+5s(<& z={}4r&p$~Q8Ua&UGcd~f2kci>m8|FZlxHnwg zumE8Pkx!()7KfpzAB#!$34Z@?@i{GkT-pNVAN}gfR(o@;_(LZbqiZXLew}yOh&2^% zuk2rk?kpLp3;s67=%^r{$p_&tlQb7gJ}mPV1W{8N%ikkm%08mJnC=ub^*}$tm$#LS zD1^*OamnW6)($h(oGT<-9OPKY@0;#`qMZR@Ask2}Tb2o!dU`ynRic-d?VjMh&Pl+{ zZDGmT%xtrrIK2F0@^2EQT&}$B`h>0ji5e2?V_z492S^W5!=I{B7;;lE|Iwcdu$J$q zzdbdG_+r{!p-HQOyTF)`CS(-_ArJR=5nH~HlFXCX;C17W6f!5ZzSL5-;rc%P-M(WA znW&Cgvi_}VYTNzkS}22;_UI*$V{zR~f4$RBIPKKqjX#dIjlm(6z)1UT_n?EiIfXS^ zbwP@qL7)2FXH0e&y=jpgJp2~gH+Zq3mt zSJCf%@UxJD$VxU>ypMK@&YYdN=;og3}je2!(*i2o;+5c8mfIWyNgQKVvrYuzNSgV}g{q zshZK@O*IyOs^r1%wE6M;D75lsr+c8kKM(~kP-Sr`&o|G;UOWQPSEhjD?QBT>(7Sac zVGzcG(&zJ69?$R%4GrJ=|MjdpIsS{POrc<>gz?>Byg()(6m3Q$imx}+&`p7=qQyyX zse?!UJ{S`uZ)9|qzkWxDQPQIWAOQFoYzEZL^%K1f3Qs*RE297z2b5~5>~1^h1Jgmu z|Mdc(yx*V(r4# ztd3s#JUVZ@(ZX^JE>jw>fa>q=dOJubmcbn;?13DPvW-pQ$Rf@@C-Mzwu0pE=ACRKQ z1om!AEB6q;R$v9#fXbQ=1gr2wbzKz-)1pd?VQcJ3%!vM2L8^@r6Y^y z6px7k6T{%dFv~EY;!~%^zG;6o4btWgLHQF#8~kfWUI2qGcMUr^cINiS1M2e{X){$k z25@Ds^@^JbSo~1wR}Mzf2aY;s{b^6E0^iGvGkyXsEmYJRZCH2BrcLUq`!4{^9+$ld z_gD%l$%D^10=W2npg(wHV)~c{u(ON0TLcLOlF0jiGrP9Jb`rN8m{BnB4$y`EwmVW7LK%X`l}X)%v0g4M$avDtaZm{#%lK&>7mop?A{>OyG*HpAEk6pDkBMVp5iEK!=mvr0-s{SnU9wQcy&mU> z^AM^l4PXO5efsp~$+d1ST|GUNk4sU2ZU;~p_%U|tJSV=8d=n1JwFko8*=uvs5-I~& zq;UgddAIMoUkgf{G~rnN@0}<{qAAU7Gyf{U-*Ev3+Z!r<2&R}`7NzaA4Uk`+-q*3>SwY8vUT9@Q zY7EBVTm&lj9dbq!_EJU;5H<$vwXmQ$2Dc;*cnBk*%}sgMz;Kp_bO5AoqjwTY*{niq z5(3Jmu)KS<4Gba=4t&8MibnxYt!Z6N{yP>@uqjYK{}<_@TUL<}tr@iKemwDq5AuKy z<*(aN{P&%!2%uM~4#s})^3o*SU;MidDnW?ZgMqCy$MTPPB8sp58r zq!rMIqXi(G3#d?|T`;EU&vSCLLtYjYH30>cu?J@wFAyk1IRzQKO~)CVg?J%c?hxnn z>C>j^b3CMs;(_O`rR2PSKW2Xup|cVWC!r5AZwUh-Jsbch0LW6fc8wS|Y&f*1mtNNe zXBq_N6P@fA2hzuS(YBkyqM~z$+kI-Bl$+zp8aZXQK?@Z&ef&NEv%rC7(MkCfev>F| z?&R%F*xqRU0t~sz;WgoZ=)u&3bs4moe{Z#-&&{?dC`b&?nK=Una^lYRQ?o}kp8}Ks@{huA)09(w z+t<-t#9)%T40aS+6GLO|Zo&3DAh$p#XrY|_7j_a8s{4s*gwt2*C^S8QK0o_mK9rRQ zb~yCS`3%}O9By6n14wah8d9BKo!#4NatAlASRHb;z|M6Yvr46IiK~c(Jp~%wkDxXT zK6Yw$4CH{I{UtH60n0`w<6CKEEoa^w5>r~!f1l~&-^q1Us-<;p<7JL^gg7v_yY`5T zptK!F%Uq14^Dy(=2y%&=phbQMRvg^BR@fVlPrD7fGRF6ZZW%D;k-I`>X8~Z6Y5IT} zRXE}e*C*&WE)2`*+8f~CzXY~;eB?BxgHLe%l^1PusU!~wol6UqtgKX}PH=;RQj zuVTg%U=KlF`$qRF2;{~ja;Po#U$t4Ctfjqea{mb>4iR2B0NQsJK0krr;;LqQL9(GD z0mz?T$yOu)q`%3;YZCAJg;${#^ldwbSA9Ny_v+O| zLGjlDTEpf~DLLke=YK^~UcnW-oCvjz7)$oYSI%F4D)CWHHzmrFe@Vz@aoaab`fJAa z{=?51?y^f6>sF$$Y}q^+^zItaEG#U5B1gIk2UUj*4-b#dufUSTRbpmP8Dn@h$4(ap zAv^KQYywwQ>ON`p86!zc4#cQ!L6g)W%M371$5jH z5``+R3OlfOVoKK4)Myp2HOe|?oIA|fQqAT}WmM2Gr~Ih7xeoWDYiBqk=ps>nev@CJ@N<8{y= zZ`FJx9tfV2T}Vi0Rrn2i5hie|fm)F)#fHM%VRoN%!69rCGJ4a_?ToGM_TuotGWs9}QB#-kJ z@_lLP-=B>ynQ256zdXh2lkm0V#$#SYNJt1+WLP~Jlr6Kevan=iv{I)Pe-I+~h9DY@ zi6Md46qQeeWNL<^n*(0csT`?G?4s|DXT%D%9Y&Oss@u330S>hx>%S3o2U^cd!l z?OGJhZh1&%zBKdvu+46<0?N(bCH*NLI!wGf4YCL5r>|_O*y-p{Mny$!0Ltzq7l$#c>W*igCDbn3FIJ1y$jLtSFVrsa=MTp1#UW8l6vhsvp_$srD_ zj0_X_FC|CEi|u1aWcP77EjD*{ltDMfdGoig%<;jmMij+Y#)6vWvV5K$2k^W(#P zPm@MCG+NSuf_HdJH{-kU_hIm3yN3nGvo zLS+wlW@x3TdxwUH*?D*(w`-;YPGqiLyCxBT?ZPR_vJDZUb0Q)`bEMTNAczi`Y0*uu zl#3TGv;ZJ)4ym1{iW}Uif{6(e4Zrc;=#5i4zq3yc!&A?+9g&9my|MpjTg*ZE(QwHAf!S9FolA5hr>n2%iQwC1Kn%5MmH>KOCE zaOC3Q<8PH??id;}z#5l`Cq^Ja`hM|U8jQ0*{K^B5M+Gl1mWl`u%ufd~XFpEElXO{VV&3I-i)6LUU)OoXQ5LElTy}ctc!z^#$+{tV3 z|0_8}s}*tU&7N(r9zLhy{QPDBtN<>ilX3UuuRNyn(F(cR{k|lgIhaP(&CN}V!p0(~ zSZz$sZN^ z!8Tc0**eXx)oyrIk1or)Ma9J}*2i{qsGH9HSso%r|J!>0yMiAz0>)|PTuHxUL>qly zry|@Ye(#>uHFfowBlgcDluds1spr0@?(rh*?Cjnwx4w7#TV-r!@Zs_z4l^+;joP)6gXI4K(Z-SAYu~;30(>Qa(g~@f5Ul|8#U$!`=UZ))LWybJrf5-iQPiRr3Nj^DWT)@T!w$?iy0$nw z`zT)Sn69a*$+I0q?$4ld;7z7a`)?x6Tx`vyVp7sikauCj%*CUBzRWoK z)=j{Ryy<`MZu6i2UI#IdGM{bhTFK#^)v+@m`xcv%xec%nj+T~I(>O;-a49u1A>{N; zyzcj~k*Ai^m-+`@X#dH_gP~IK&)sFAVPTS!hkSlX1h}||*`rs~+aTFM6A&cx~*qHJ8^XCbfnU0%fyEzakSU>>9|JyLeoDPr@C^#9f>gFr9gQ!l0 zg$>)kLOlr4v}r22c&$&hf&EJWkuZ?66RrL^RWAiYDUqOQ(ZhD0@!q|AO#^vqVBVs@ zD!t|N?}=Q1Fb!N0swu!BD0ri?$QrCg!~Wt~2+BvE7x@JT2Ma#<;{aI4r*nnvlkXf+ z0XA3%yu!MO{wy>fd+hIOpi1A+=?fHK2x9WVqX{yZ*?uLCE1*xj4vM5wy zOatxKhui7XGgTv|O6uwnqdzMLZG}5JI^;DqNu#2pjcEVe;HJWYyeGs-s9*rvZ&i@m z&d<#~b72NoZ~*8DV7hzOfBzQyX_6LJetuF&XeiNl z(YeDR_*6qvuCmV%RNwQ|;#J+;rCk1_XY*C!Xu$boO z&!6HhGYG^at(vhBIco9DsN02tWjL^%M`gwh{g^x94pn>vu}_9vfa$6(=OZG7`29g% zfX)2UQaCtyRL`56hettG6+ezq0__DZyU8JqN@zm4a|5s&c3Ih}b7Af8pY82=^CEm; zr$S#ME~c}yvk-i>r#m-9K7LGTX>C2%k;uV)@glX9NHdO?mlteCTtME~p&Hlw_qmj^ z@hNM@vLoB~ zwzP^%c3n5PRA^M<)oLsKSIW9Z8Mn8bg-%9HFoC zPgc-AG<*=3-edhzm-qYP;vBeM@qlA#7{qAR9JtqMDHIYOeismGG)|kFpO>I7SECL1 z_4Btc^xdM_M$okQljLOVcvdnvo80bhjVWTZ>Ie^K1`bu@GK)#RdJag^2f*0*0TN3p zB1rh&OJRAU`qIIlwdkK;3R^6Na`W?XA-C@Ul_#)MIYV}Bnm5bq>gy@ao{g@l5#L## zW;y!1Tj;e<>9RI1!e?AV{^ZG%jg1X?4Gj|5hXOz#0g`vy$IfX_pLW{&*(jDGeBVJA z5ZZj$Sy=-s(imd#L7NiLD0u}1oD$RegVEhCbR$8ItzmC^Ek-p@!Q7k;u9V-bfuum^ ziWcE`jy5+rOrwBG9;!bKOWxevTzJ^TVrzT5%zM+~TRm`t_gM+iH#awvv#`X&O5^0@ zbdijlf5A5B-nwN#-2!h9oyZ4_ z?Tu5mIDU#1aLq(QT%A){Nd^8M($*FbD!n7j(59(H4(h5{Vqzs-T`E|I{e@aAJHI9; z3JTxe$47BiSXjlTbuU3l2lP&DFA^y#DLF2?I113c1*8gek$ac@O{l1mVFqDaa@oC^ zQ+xaSRkfK-cLH^aTH%=+viy`Q;e9s@IvjC;qBTxVW+r(n`<+iBgd`NML&?hOoYS|O zN9khD?~Zw^imo3W9igu@BqW5Dlk1btV~y|8pGo}(dKF4lC~he-Q3lM#v{VGDbk9CUuMBL=f#x_slZ{QZh zoTrq%y=z8`$O+h!I%if^h{1K0-DJ~Tg_Ox-GgbWAxaOSgLxf$0?;06%L0qrQpc-Ca z>p-3XyNd9B+|IyzwS$*Pf;hjmB1bE;huHeFAKy6qbrE&OTeHdz|L=FY7J(h+IC6F( zGBPsWW6n4tgfQ<^0y#U)2b@G%UW7A-R8}z}?FS>LBYc^T$uERla6m$R9F8p@xrcRS zED>i2>Qxv#f2#k_K}GBAK$BX;yi41$xCiND;R=$#`}Sg#;R*ySY|0w&mrCpZf5dw| zLdbOs8wCfY%dQlPtzeP!MLOxiau+f;>qOo$RVUh)YU-pa`rUrKecI-tr&$`t?p7PW{F#RjPQDjxrc`ByXYaIh)*Zz;jQ0dr?wC3M;)0P-9dMw(fAN3sQ1g@tcYn*|1N6 z&?*pgn`jtfU-US_8cuxtI0yRKI>*MM>$@k`E6R5lDc>Q~(ZSoY-mE47fN! z@T0#fM*e<&0?^KYVr_t<>8*bR?alncLia4%S=z30t|7bM6$oqXNw{^z{#$Pz3ZsI; z!n=S3(KwqK8b+b~?#&^DXe;0)+5iLsWEdO%eomCFUb6U0CC7??%| z2X&%%(U{ZZu;BG?!+;ZNgf2`1yG+1(1%A+UbFnX*%*38~ z{bfqxmBj!t9Ha*9spBAs&{GGzhnp`0$&LD@G!z|_+DT`z7gD?A99M!xi35SbZs}Cc zy{w2y2!nV%i9*yV;qV;yLPJASCk`5mi{)^UFmqW;gFkr3ZtNxUZ))iPmxJoJQh|>7 z_l`v9u_l3-QMofz{TjGFaNwb!SBXaPXfrAmQRs|M{~4qiXV^hBV55d?!sM+CFYhwk zUCaBv$ePEMmBO`pe3g3l?(v{h_7Lk-9zQkaSA)DCT{yt$0B;hTLgwm+!jh<1CMf?x zFxhA+RP}Hef*u$?#XwCgXd!RF7Hz&K9%gnmEaH8#Jk;(#>V(q|z{4zT4sW#yiovY> zd`>9v>nFU^xp{LFN#v05^}snB5!6ig#gGIPD%T+WBy2-KuzAknLVdUfr@ z17BYY0NB&SU7xe=FMR)g2f(bLN|lV1CI``WT59Sd=4ypn`>H7@GfT6wvXaP4FBMUw zQZO;ag0}hQxGI20DVNOR)+Va+AnI#byD!P~)lOva%a?)wLbvph!-%^Qu5-A+8HQVB z7z^MgX|*$y`Cw6(7{3pKI!z-$rz3~xl|k`qdmP`@_eA&gkkrIPPLF0FnGi#4H1gFg zh0;~RkIY8W>lY(x?m$U(qA~#XVLLsA+Ah=`M=Pg6kptb55Cz7~$1tlZA(1k$GZMNM zE=Y}+v|w`b^Oai!x`mtu*-IkQI|zgkgg6i71t{&+0B30ya3pdJt?5d61EY*f@O+@4 zIV*6vS?obd<*;4V9&}(hOqt~kpbrR0#xFuJ-Qzm0gol)cYn(UI*4H1MEstfQL696> zUNHd@_u=EphzoP`mU=gCJVHs=_m*FmGB3ZiA_hAT@ZbG9HdneoJ7eG@a#Ttq$6Lzz zbaZskgbOl`QR>$hg)xDnY$ipzzsf9?qG7XGr&ujv1V*#YxZXnZWXFqM&WWg`9HbTi z6NrYt8~+Z|TApjpeM~%MarFTse~yjhegJ@g&XU0g`wY6lqr;p+c!hUs!8eig9(TUU z(LoHt`-C^uJniFn${E-D5RH4b1Mw0%#aL$1MkFjE;+imYlAN3@U*PLtGe$bVjhP+Y z_A1sX65r>Sg-1kqLGKOvX~3>$Z;p_uB@v= zxQp^w~4LO_tyxFr~3RqH@a1mf`dDki91pdC373?y_-Fx@-Cg6EGs z!!0$;g`XMx1}&oN65SfEfV}L@1h@B!9wan2%KL!=9^A>I z#A`Js8DAWIaYl5z~H{r+Xl~t zZ)&MIl-L_i?~|SIf&+b804Yml_-r;nS;W%H%KYog8ykQAEK1ad`$Lf%g{j}vLqY?y zIxK*`0X(tp@)wLG%sa(NCR5s{>8E7Ex=*(1>J_C%5N#8aOjAd zjsNgLQX2Z|q^+!a%WwSFE*rwmx$dWzQ8Ofpc!twR7H5+5d$q)$Uv7VM9OqbrS2@ws7a+--k01RH9ecKT|8TS>j^8wUJ6 zI%_epYB@x_TY*ne`L>P@rpmC#$Pcz2?51)5O@cr;z^3V&N-GG`GrBj@($k}@x>7a| zzN!`LeQC=UPhz@S@HnY}{XI7VX44i7^hdmeW)*40)=vu#u4EPlH0aPm4>4^OeC;9{ z+wqyIp>G_v^sCIh&LaShPXVk8vD)r#7+{?B&E?LLS{@Ua&(GbAo?x0 zG!$FifN#{4#@C?Mv$*HNfJ~feX**Z8U-P~0KTu2=pEEs4k%S-}ZSl~&i`g~3g$AL_ zspPq3e6@zfdVQw=1!|1DfyCd070FCT_mV(>=-ZAUtFnu4Ju}o~v8NZD8eT(x7@LH| z(+Y30S1(`6L!D_oY1Puwa;A2@Vg8&aATChWf$l`{XdhU{ko4vE;@&ASEH>@Af2S9w zSfF{Jmk5ik!MVd{=1ATw7TnZ0mVuz9oQAzQVoeK~ULYaZgz2V@VK(i;FXljz0X3Pr zR)Sx#d>dO^ih%QSZH6%a)xx%WkhC0{7U*ocEj5l^2sG0v(lsmNOUE|Og%V<2ImgDv z0739I&DQYzAQ zW4>LaYq9%lGNav*xT_q^XX2P;>~3d(1C(gRD2M1rTa7vX`t>XL7VnUX?8Iyp93GIa z+N%XyUdR+K>)}_YkkO{B0mP;+s||b^Ugl_rrO+&p&CFF^m$fh)T!cTOWytiq8zPT` z$(cpo@=2K#>zzQS67gZZ{a^ZZyheZ671zSd!$75$KN1L`XgMxj>RUeIH?50MiKdAZ z$}GEDfR_8jqpN|h?4_<=>17Le34!p>**TRJXJ|m$vR#!+e3pmc7Y*4x^d+iRh%r4V zPfJU?$JcdCEFXwnJ7e)AA_5Bp9&K~I_aR|TlX)Nt1uvi8)lJF!%OT0hRFKe)QL91x z0H|)A=iy33Y3U`HO|#vk>;Om^DkwRaK=rnZU`Sr+@>b)JdkA6$eScIysE-Fo%u$$? z!6hVEp#?AU)EB!dKy%w7C^#gIry)X^yA-fbyl&s?)e0r=SFeZwo_=?s7w!84>@}{u zJhtghBlJHC*!HvH;^TYPtU-y>Q&*3ZGmN5ag^47ejc#2&8uTULgesp7&ZR+n)OVUgHG zW)jcH!}@@Y+}E+NC0D^GMIjtX&gS_5dW;0TO70Vf7n^;|s-{1|x(Zq&CsuUU1v*8Q z0eAwW62>?#W&roGRE!;Rg_%zw{kK;Au!+24Vq&1p+B7%zo8P!5P{Mt&pUzit)%|oM z4cN>ch(}(LOFexj?f~6JU=hCEmz>lOGk@4#zUmA~q7;{6{tGCRqdl-tPaXN?314W2 z&c}IGe2$)3jg{3@_sX-Bxw9+t zqP2+s8sG5XO_KS)7_viKVDt0yH_D0F0dr%ABo&fiMk*?gLy-G@?Bj912s4ukr=^Ac zzeN&gPSQeRWgto;&sDa~-kR9C;09HtTwwJ_0wG{gEY>5I{KCRUd5d6%GVn4$JJ){9;;|5*LsN?L3xS_2Dw@hAig$wk E55j + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + From 580f50e736cf7d237c5d824afd78881b55b3023b Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 13 Jan 2021 16:12:04 +0300 Subject: [PATCH 189/755] Add very basic example --- Cargo.toml | 4 ++++ examples/self_info.rs | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 examples/self_info.rs diff --git a/Cargo.toml b/Cargo.toml index 09993956..1cae30f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,3 +60,7 @@ full = ["throttle", "cache_me", "auto_send"] [package.metadata."docs.rs"] all-features = true rustdoc-args = ["--cfg", "docsrs", "-Znormalize-docs"] + +[[example]] +name = "self_info" +required-features = ["tokio/macros", "tokio/rt-multi-thread", "auto_send"] diff --git a/examples/self_info.rs b/examples/self_info.rs new file mode 100644 index 00000000..78678e9f --- /dev/null +++ b/examples/self_info.rs @@ -0,0 +1,23 @@ +use teloxide_core::{ + prelude::*, + types::{DiceEmoji, ParseMode}, +}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let chat_id = std::env::var("CHAT_ID") + .expect("Expected CHAT_ID env var") + .parse::()?; + + let bot = Bot::from_env() + .parse_mode(ParseMode::MarkdownV2) + .auto_send(); + + let me = bot.get_me().await?; + + bot.send_dice(chat_id, DiceEmoji::Dice).await?; + bot.send_message(chat_id, format!("Hi, my name is **{}** 👋", me.first_name)) + .await?; + + Ok(()) +} From 2ee7bb22a1f4e3cf179b029b4c0b47e04232c1b6 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 13 Jan 2021 16:14:02 +0300 Subject: [PATCH 190/755] Move `client_from_env` from root into `net` --- src/lib.rs | 26 -------------------------- src/net/mod.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d1105eaf..79f43df4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,29 +35,3 @@ mod errors; // implementation details mod serde_multipart; - -/// Constructs a client from the `TELOXIDE_PROXY` environmental variable. -/// -/// This function passes the value of `TELOXIDE_PROXY` into -/// [`reqwest::Proxy::all`], if it exists, otherwise returns the default -/// client. -/// -/// # Note -/// The created client will have safe settings, meaning that it will be able to -/// work in long time durations, see the [issue 223]. -/// -/// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all -/// [issue 223]: https://github.com/teloxide/teloxide/issues/223 -pub fn client_from_env() -> reqwest::Client { - use crate::bot::{sound_bot, TELOXIDE_PROXY}; - use reqwest::Proxy; - - let builder = sound_bot(); - - match std::env::var(TELOXIDE_PROXY).ok() { - Some(proxy) => builder.proxy(Proxy::all(&proxy).expect("creating reqwest::Proxy")), - None => builder, - } - .build() - .expect("creating reqwest::Client") -} diff --git a/src/net/mod.rs b/src/net/mod.rs index 944e1538..6c9665dc 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -14,6 +14,38 @@ mod telegram_response; /// The default Telegram API URL. pub const TELEGRAM_API_URL: &str = "https://api.telegram.org"; +/// Constructs a network client from the `TELOXIDE_PROXY` environmental +/// variable. +/// +/// This function passes the value of `TELOXIDE_PROXY` into +/// [`reqwest::Proxy::all`], if it exists, otherwise returns the default +/// client. +/// +/// ## Note +/// +/// The created client will have safe settings, meaning that it will be able to +/// work in long time durations, see the [issue 223]. +/// +/// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all +/// [issue 223]: https://github.com/teloxide/teloxide/issues/223 +/// +/// ## Panics +/// +/// If `TELOXIDE_PROXY` exists, but isn't correct url. +pub fn client_from_env() -> reqwest::Client { + use crate::bot::{sound_bot, TELOXIDE_PROXY}; + use reqwest::Proxy; + + let builder = sound_bot(); + + match std::env::var(TELOXIDE_PROXY).ok() { + Some(proxy) => builder.proxy(Proxy::all(&proxy).expect("creating reqwest::Proxy")), + None => builder, + } + .build() + .expect("creating reqwest::Client") +} + /// Creates URL for making HTTPS requests. See the [Telegram documentation]. /// /// [Telegram documentation]: https://core.telegram.org/bots/api#making-requests From 84df33dbefbc1da6b34bbe6cb81109bcb68854e4 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 15 Jan 2021 21:51:15 +0300 Subject: [PATCH 191/755] improve docs --- README.md | 25 +++++++++++++++- src/adaptors.rs | 15 ++++++++++ src/adaptors/auto_send.rs | 19 ++++++++---- src/bot/mod.rs | 57 +++++++++++++++++++++++++++++++++-- src/errors.rs | 2 +- src/lib.rs | 62 +++++++++++++++++++++++++++++++++++++-- src/local_macros.rs | 2 ++ src/payloads/mod.rs | 5 ++-- src/prelude.rs | 6 +++- src/requests/request.rs | 8 +++-- src/requests/requester.rs | 35 +++++++++++++++++++++- 11 files changed, 217 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index e6ad7315..0e01f9b1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,30 @@ +

+ # teloxide-core [![CI status](https://github.com/teloxide/teloxide-core/workflows/Continuous%20integration/badge.svg)](https://github.com/teloxide/teloxide-core/actions) +[![documentation](https://docs.rs/teloxide_core/badge.svg)](https://docs.rs/teloxide_core/) [![documentation (master)](https://img.shields.io/badge/docs-master-blue)](https://teloxide-core.netlify.com) [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Api Cov](https://img.shields.io/badge/API%20coverage-Up%20to%200.4.9%20(inclusively)-green.svg)](https://core.telegram.org/bots/api) +[![crates.io](https://img.shields.io/crates/v/teloxide_core.svg)](https://crates.io/crates/teloxide_core) +[![Official Chat](https://img.shields.io/badge/official%20chat-t.me%2Fteloxide-blueviolet)](https://t.me/teloxide) -Core part of `teloxide` library. + + +Core part of the [`teloxide`] library. + +This library provides tools for making requests to the [Telegram Bot API] +(Currently, version `4.9` is supported) with ease. The library is fully +asynchronouns and built using [`tokio`]. + +```toml +teloxide_core = "0.1" +``` +_Compiler support: requires rustc 1.49+_ + +[`teloxide`]: https://docs.rs/teloxide +[Telegram Bot API]: https://core.telegram.org/bots/api +[`tokio`]: https://tokio.rs \ No newline at end of file diff --git a/src/adaptors.rs b/src/adaptors.rs index 1a308c57..8692d43c 100644 --- a/src/adaptors.rs +++ b/src/adaptors.rs @@ -8,12 +8,27 @@ //! //! [`Requester`]: crate::requests::Requester +/// [`AutoSend`] bot adaptor which allows sending a request without calling +/// [`send`]. +/// +/// [`AutoSend`]: auto_send::AutoSend +/// [`send`]: crate::requests::Request::send #[cfg(feature = "auto_send")] #[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "auto_send")))] pub mod auto_send; + +/// [`CacheMe`] bot adaptor which caches [`GetMe`] requests. +/// +/// [`CacheMe`]: cache_me::CacheMe +/// [`GetMe`]: crate::payloads::GetMe #[cfg(feature = "cache_me")] #[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "cache_me")))] pub mod cache_me; + +/// [`Throttle`] bot adaptor which allows automatically throttle when hitting +/// API limits. +/// +/// [`Throttle`]: throttle::Throttle #[cfg(feature = "throttle")] #[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "throttle")))] pub mod throttle; diff --git a/src/adaptors/auto_send.rs b/src/adaptors/auto_send.rs index 890979ac..77ce8e25 100644 --- a/src/adaptors/auto_send.rs +++ b/src/adaptors/auto_send.rs @@ -20,8 +20,8 @@ use crate::{ /// Notes: /// 1. This wrapper should be the most outer i.e.: `AutoSend>` /// will automatically send requests, while `CacheMe>` - won't. -/// 2. After first call to `poll` on a request you will unable to access payload -/// nor could you use [`send_ref`](Request::send_ref). +/// 2. After first call to `poll` on a request you will be unable to access +/// payload nor could you use [`send_ref`](Request::send_ref). /// /// ## Examples /// @@ -74,7 +74,10 @@ macro_rules! fty { }; } -impl Requester for AutoSend { +impl Requester for AutoSend +where + B: Requester, +{ type Err = B::Err; requester_forward! { @@ -112,7 +115,10 @@ download_forward! { #[pin_project::pin_project] pub struct AutoRequest(#[pin] Inner); -impl AutoRequest { +impl AutoRequest +where + R: Request, +{ pub fn new(inner: R) -> Self { Self(Inner::Request(inner)) } @@ -133,7 +139,10 @@ enum Inner { Done, } -impl Request for AutoRequest { +impl Request for AutoRequest +where + R: Request, +{ type Err = R::Err; type Send = R::Send; type SendRef = R::SendRef; diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 07c78829..06f6cea6 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -22,9 +22,41 @@ pub(crate) const TELOXIDE_PROXY: &str = "TELOXIDE_PROXY"; /// A requests sender. /// -/// No need to put it into [`Arc`], because it's already in. +/// This is the main type of the library, it allows to send requests to the +/// [Telegram Bot API] and download files. +/// +/// ## TBA methods +/// +/// All TBA methods are located in the [`Requester`] [`impl for Bot`]. This +/// allows for opt-in behaviours using requester [adaptors]. +/// +/// ``` +/// # async { +/// use teloxide_core::prelude::*; +/// +/// let bot = Bot::new("TOKEN"); +/// dbg!(bot.get_me().send().await?); +/// # Ok::<_, teloxide_core::RequestError>(()) }; +/// ``` +/// +/// [`Requester`]: crate::requests::Requester +/// [`impl for Bot`]: Bot#impl-Requester +/// [adaptors]: crate::adaptors +/// +/// ## File download +/// +/// In the similar way as with TBA methods, file downloading methods are located +/// in a trait — [`Download<'_>`]. See its documentation for more. +/// +/// [`Download<'_>`]: crate::net::Download +/// +/// ## Clone cost +/// +/// `Bot::clone` is relatively cheap, so if you need to share `Bot`, it's +/// recommended to clone it, instead of wrapping it in [`Arc<_>`]. /// /// [`Arc`]: std::sync::Arc +/// [Telegram Bot API]: https://core.telegram.org/bots/api #[derive(Debug, Clone)] pub struct Bot { token: Arc, @@ -32,6 +64,7 @@ pub struct Bot { client: Client, } +/// Constructors impl Bot { /// Creates a new `Bot` with the specified token and the default /// [http-client](reqwest::Client). @@ -81,7 +114,7 @@ impl Bot { /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html /// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all pub fn from_env() -> Self { - Self::from_env_with_client(crate::client_from_env()) + Self::from_env_with_client(crate::net::client_from_env()) } /// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a @@ -122,11 +155,31 @@ impl Bot { /// bot.get_me().send().await /// # }; /// ``` + /// + /// ## Multi-instance behaviour + /// + /// This method only sets the url for one bot instace, older clones are + /// unaffected. + /// + /// ``` + /// use teloxide_core::Bot; + /// + /// let bot = Bot::new("TOKEN"); + /// let bot2 = bot.clone(); + /// let bot = bot.set_api_url(reqwest::Url::parse("https://example.com/").unwrap()); + /// + /// assert_eq!(bot.api_url().as_str(), "https://example.com/"); + /// assert_eq!(bot.clone().api_url().as_str(), "https://example.com/"); + /// assert_ne!(bot2.api_url().as_str(), "https://example.com/"); + /// ``` pub fn set_api_url(mut self, url: reqwest::Url) -> Self { self.api_url = ApiUrl::Custom(Arc::new(url)); self } +} +/// Getters +impl Bot { /// Returns currently used token. pub fn token(&self) -> &str { &self.token diff --git a/src/errors.rs b/src/errors.rs index 3476ceed..4efcae04 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -53,7 +53,7 @@ pub enum RequestError { #[error("An error while parsing JSON: {0}")] InvalidJson(#[source] serde_json::Error), - // Occurs when trying to send a file to Telegram. + /// Occurs when trying to send a file to Telegram. #[error("An I/O error: {0}")] Io(#[source] io::Error), } diff --git a/src/lib.rs b/src/lib.rs index 79f43df4..797c36a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,69 @@ -//! Core part of `teloxide` library. -// TODO: expand docs +//! Core part of the [`teloxide`] library. +//! +//! This library provides tools for making requests to the [Telegram Bot API] +//! (Currently, version `4.9` is supported) with ease. The library is fully +//! asynchronouns and built using [`tokio`]. +//! +//!```toml +//! teloxide_core = "0.1" +//! ``` +//! _Compiler support: requires rustc 1.49+_ +//! +//! ``` +//! # #[cfg(feature = "auto_send")] +//! # async { +//! # let chat_id = 0; +//! use teloxide_core::{ +//! prelude::*, +//! types::{DiceEmoji, ParseMode}, +//! }; +//! +//! let bot = Bot::from_env() +//! .parse_mode(ParseMode::MarkdownV2) +//! .auto_send(); +//! +//! let me = bot.get_me().await?; +//! +//! bot.send_dice(chat_id, DiceEmoji::Dice).await?; +//! bot.send_message(chat_id, format!("Hi, my name is **{}** 👋", me.first_name)) +//! .await?; +//! # Ok::<_, Box>(()) }; +//! ``` +//! +//!
+//! +//!
+//! +//! [`teloxide`]: https://docs.rs/teloxide +//! [Telegram Bot API]: https://core.telegram.org/bots/api +//! [`tokio`]: https://tokio.rs +//! +//! ## Cargo features +//! +//! - `auto_send` — enables [`AutoSend`] bot adaptor +//! - `throttle` — enables [`Throttle`] bot adaptor +//! - `cache_me` — enables [`CacheMe`] bot adaptor +//! - `full` — enables all features except `nigthly` +//! - `nightly` — enables nigthly-only features, currently: +//! - Removes some future boxing using `#![feature(type_alias_impl_trait)]` +//! - Used to built docs (`#![feature(doc_cfg, doc_spotlight)]`) +//! +//! [`AutoSend`]: adaptors::AutoSend +//! [`Throttle`]: adaptors::Throttle +//! [`CacheMe`]: adaptors::CacheMe +#![doc( + // FIXME(waffle): use github + html_logo_url = "https://cdn.discordapp.com/attachments/224881373326999553/798598120760934410/logo.png", + html_favicon_url = "https://cdn.discordapp.com/attachments/224881373326999553/798598120760934410/logo.png" +)] +#![forbid(unsafe_code)] // we pass "--cfg docsrs" when building docs to add `This is supported on feature="..." only.` // // To properly build docs of this crate run // ```console // $ RUSTDOCFLAGS="--cfg docsrs -Znormalize-docs" cargo doc --open --all-features // ``` -#![forbid(unsafe_code)] #![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg, doc_spotlight))] #![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))] #![cfg_attr(feature = "full", deny(broken_intra_doc_links))] diff --git a/src/local_macros.rs b/src/local_macros.rs index 25cffd7f..91f5623a 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -188,6 +188,8 @@ macro_rules! impl_payload { #[allow(clippy::too_many_arguments)] // It's just easier for macros to generate such code. #[allow(clippy::redundant_field_names)] + // It's obvious what this method does. (If you think it's not, feel free to open a PR) + #[allow(missing_docs)] $vi fn new($($($fields : impl_payload!(@convert? $FTy $([$conv])?)),*)?) -> Self { Self { $( diff --git a/src/payloads/mod.rs b/src/payloads/mod.rs index abfebc1b..cf31ea9d 100644 --- a/src/payloads/mod.rs +++ b/src/payloads/mod.rs @@ -1,7 +1,8 @@ //! Request data sent to Telegram. -/// This module re-exports all the setters traits as `_`. When used with a glob -/// import: +/// This module re-exports all the setters traits as `_`. +/// +/// When used with a glob import: /// /// ``` /// use teloxide_core::payloads::setters::*; diff --git a/src/prelude.rs b/src/prelude.rs index 3ea2b60c..ed53d1d7 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,3 +1,7 @@ //! Commonly used items. -pub use crate::requests::Requester; +pub use crate::{ + payloads::setters::*, + requests::{Request, Requester, RequesterExt}, + Bot, +}; diff --git a/src/requests/request.rs b/src/requests/request.rs index b719abf5..8517daa2 100644 --- a/src/requests/request.rs +++ b/src/requests/request.rs @@ -13,7 +13,7 @@ use crate::requests::{HasPayload, Output}; /// /// This is crucial for request wrappers which may want to cancel and/or never /// send the underlying request. E.g.: [`Throttle`]'s `send_ref` calls -/// `B::send_ref` while _not_ meaning to really send the request right now. +/// `B::send_ref` while _not_ meaning to really send the request at the moment. /// /// [`Throttle`]: crate::adaptors::Throttle #[cfg_attr(all(docsrs, feature = "nightly"), doc(spotlight))] @@ -32,8 +32,8 @@ pub trait Request: HasPayload { /// A type of the future returned by the [`send_ref`](Request::send_ref) /// method. - // Note: it intentionally forbids borrowing from `self` though anyway we - // couldn't allow borrowing without GATs. + // Note: it intentionally forbids borrowing from `self` though we couldn't allow + // borrowing without GATs anyway. type SendRef: Future, Self::Err>> + Send; /// Send this request. @@ -49,6 +49,8 @@ pub trait Request: HasPayload { /// }; /// /// let bot = Bot::new("TOKEN"); + /// + /// // Note: it's recommended to `Requester` instead of creating requests directly /// let method = GetMe::new(); /// let request = JsonRequest::new(bot, method); /// let _: User = request.send().await.unwrap(); diff --git a/src/requests/requester.rs b/src/requests/requester.rs index 09ff0641..7e30be76 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -15,9 +15,42 @@ use crate::{ /// /// This trait is implemented by all bots & bot adaptors. /// -/// _This trait is included in the crate's [`prelude`](crate::prelude)_. +/// ## Examples +/// +/// Calling TBA methods: +/// +/// ``` +/// # async { +/// use teloxide_core::{prelude::*, types::ParseMode}; +/// +/// // Bot implements `Requester` +/// let bot = Bot::new("TOKEN"); +/// +/// // Required parameters are supplied to the `Requester` methods: +/// bot.send_message(0, "Text") +/// // Optional parameters can be supplied by calling setters +/// .parse_mode(ParseMode::HTML) +/// // To send request to telegram you need to call `.send()` and await the resulting future +/// .send() +/// .await?; +/// # Ok::<_, teloxide_core::RequestError>(()) }; +/// ``` +/// +/// Using `Requester` in a generic context: +/// +/// ``` +/// use teloxide_core::{prelude::*, types::Message}; +/// +/// async fn send_hi(bot: R, chat: i64) -> Message +/// where +/// R: Requester, +/// { +/// bot.send_message(chat, "hi").send().await.expect("error") +/// } +/// ``` #[cfg_attr(all(docsrs, feature = "nightly"), doc(spotlight))] pub trait Requester { + /// Error type returned by all requests. type Err: std::error::Error + Send; // This block is auto generated by `cg` (de3765b). From b93947c0ab2da8719605456609b0f541075c27b4 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 15 Jan 2021 22:19:42 +0300 Subject: [PATCH 192/755] stop using serde internall module --- src/types/non_telegram_types/mime.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/types/non_telegram_types/mime.rs b/src/types/non_telegram_types/mime.rs index c5655cc7..34422e34 100644 --- a/src/types/non_telegram_types/mime.rs +++ b/src/types/non_telegram_types/mime.rs @@ -70,10 +70,7 @@ struct MimeVisitor; impl<'a> Visitor<'a> for MimeVisitor { type Value = MimeDe; - fn expecting( - &self, - formatter: &mut fmt::Formatter<'_>, - ) -> Result<(), serde::export::fmt::Error> { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { formatter.write_str("mime type") } From cc9e3a56e6084f63fddfb3c11861a753bd4a4749 Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 21 Jan 2021 09:45:20 +0300 Subject: [PATCH 193/755] workaround rustc bug --- src/local_macros.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/local_macros.rs b/src/local_macros.rs index 91f5623a..eaf50554 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -47,7 +47,15 @@ macro_rules! req_future { $(where $($wh:tt)*)? ) => { #[pin_project::pin_project] - $v struct $i<$T> + pub + // FIXME(waffle): + // The `pub` above should ideally be `$v`, but we currently can't do + // this due to compiler bug, see: + // - pin_project bug report + // - related rustc issue + // - original fix (closed) + // - second iteration of the fix + struct $i<$T> $(where $($wh)*)? { #[pin] From 3aec4e5070fa92e95eea2068b8aec2b188819b3e Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 21 Jan 2021 10:15:02 +0300 Subject: [PATCH 194/755] fix test --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 797c36a7..cca00141 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,7 @@ //! //! let me = bot.get_me().await?; //! -//! bot.send_dice(chat_id, DiceEmoji::Dice).await?; +//! bot.send_dice(chat_id).emoji(DiceEmoji::Dice).await?; //! bot.send_message(chat_id, format!("Hi, my name is **{}** 👋", me.first_name)) //! .await?; //! # Ok::<_, Box>(()) }; From 6abf571c96d6b021f7696199adb27bd145d1e0be Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 12 Jan 2021 13:13:53 +0300 Subject: [PATCH 195/755] Remove git dependency --- Cargo.toml | 5 ++--- src/adaptors/throttle.rs | 47 ++++++++++++++++++++-------------------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1cae30f1..5ba8c745 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,8 +34,7 @@ thiserror = "1.0.20" once_cell = "1.5.0" never = "0.1.0" -# FIXME(waffle): use crates.io once published -vecrem = { git = "https://github.com/WaffleLapkin/vecrem", rev = "6b9b6f42342df8b75548c6ed387072ff235429b1" } +vecrem = { version = "0.1", optional = true } [features] default = [] @@ -47,7 +46,7 @@ default = [] nightly = [] # Throttling bot adaptor -throttle = [] +throttle = ["vecrem"] # CacheMe bot adaptor cache_me = [] diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index 75d79a80..de56338a 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -267,34 +267,33 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender) *hchats_s.entry(*chat).or_insert(0) += 1; } - let mut queue_rem = queue.removing(); - while let Some(entry) = queue_rem.next() { - let chat = &entry.value().0; - let cond = { - hchats_s.get(chat).copied().unwrap_or(0) < limits.chat_s - && hchats.get(chat).copied().unwrap_or(0) < limits.chat_m - }; + { + let mut queue_rem = queue.removing(); + while let Some(entry) = queue_rem.next() { + let chat = &entry.value().0; + let cond = { + hchats_s.get(chat).copied().unwrap_or(0) < limits.chat_s + && hchats.get(chat).copied().unwrap_or(0) < limits.chat_m + }; - if cond { - { - *hchats_s.entry(*chat).or_insert(0) += 1; - *hchats.entry(*chat).or_insert(0) += 1; - history.push_back((*chat, Instant::now())); + if cond { + { + *hchats_s.entry(*chat).or_insert(0) += 1; + *hchats.entry(*chat).or_insert(0) += 1; + history.push_back((*chat, Instant::now())); + } + + // This will close the channel unlocking associated request + drop(entry.remove()); + + // We've "sent" 1 request, so now we can send 1 less + allowed -= 1; + if allowed == 0 { + break; + } } - - // This will close the channel unlocking associated request - drop(entry.remove()); - - // We've "sent" 1 request, so now we can send 1 less - allowed -= 1; - if allowed == 0 { - break; - } - } else { - entry.skip(); } } - drop(queue_rem); // It's easier to just recompute last second stats, instead of keeping // track of it alongside with minute stats, so we just throw this away. From e2897290558850d98beeef35bc11a7db2e9aa7c7 Mon Sep 17 00:00:00 2001 From: Mikail Bagishov Date: Wed, 11 Nov 2020 20:16:08 +0300 Subject: [PATCH 196/755] Support rustls This was teloxide-based applications can be easily compiled for musl targets. --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 1cae30f1..5cd5b149 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,9 @@ vecrem = { git = "https://github.com/WaffleLapkin/vecrem", rev = "6b9b6f42342df8 [features] default = [] +rustls = ["reqwest/rustls"] +native-tls = ["reqwest/native-tls"] + # Features which require nightly compiler. # # Currently the only used compiler feature is feature(type_alias_impl_trait) From 56f0fc609dc26e84b21b4cad218b69a76d31e283 Mon Sep 17 00:00:00 2001 From: Mikail Bagishov Date: Thu, 12 Nov 2020 00:02:51 +0300 Subject: [PATCH 197/755] fixup --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5cd5b149..bb1cda78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ vecrem = { git = "https://github.com/WaffleLapkin/vecrem", rev = "6b9b6f42342df8 [features] default = [] -rustls = ["reqwest/rustls"] +rustls = ["reqwest/rustls-tls"] native-tls = ["reqwest/native-tls"] # Features which require nightly compiler. From 3c70ae5de04e8eec18f8cf375fbba6ecd1509cb4 Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 21 Jan 2021 23:45:07 +0300 Subject: [PATCH 198/755] make native-tls default feature --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bb1cda78..56986847 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ never = "0.1.0" vecrem = { git = "https://github.com/WaffleLapkin/vecrem", rev = "6b9b6f42342df8b75548c6ed387072ff235429b1" } [features] -default = [] +default = ["native-tls"] rustls = ["reqwest/rustls-tls"] native-tls = ["reqwest/native-tls"] From 55c02e3311221182db46219c93479c0097f778b3 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 23 Jan 2021 15:45:58 +0300 Subject: [PATCH 199/755] Use edition-2018 style modules Use `module_name.rs` instead of `module_name/mod.rs`. Pros: - It's easier to find the module file (especially for modules with lots of submodules like `types`) - Module file name is not 'magical' --- src/{bot/mod.rs => bot.rs} | 0 src/{net/mod.rs => net.rs} | 0 src/{payloads/mod.rs => payloads.rs} | 0 src/{requests/mod.rs => requests.rs} | 0 src/{types/mod.rs => types.rs} | 9 +++++++-- src/types/non_telegram_types/mod.rs | 9 --------- 6 files changed, 7 insertions(+), 11 deletions(-) rename src/{bot/mod.rs => bot.rs} (100%) rename src/{net/mod.rs => net.rs} (100%) rename src/{payloads/mod.rs => payloads.rs} (100%) rename src/{requests/mod.rs => requests.rs} (100%) rename src/{types/mod.rs => types.rs} (95%) delete mode 100644 src/types/non_telegram_types/mod.rs diff --git a/src/bot/mod.rs b/src/bot.rs similarity index 100% rename from src/bot/mod.rs rename to src/bot.rs diff --git a/src/net/mod.rs b/src/net.rs similarity index 100% rename from src/net/mod.rs rename to src/net.rs diff --git a/src/payloads/mod.rs b/src/payloads.rs similarity index 100% rename from src/payloads/mod.rs rename to src/payloads.rs diff --git a/src/requests/mod.rs b/src/requests.rs similarity index 100% rename from src/requests/mod.rs rename to src/requests.rs diff --git a/src/types/mod.rs b/src/types.rs similarity index 95% rename from src/types/mod.rs rename to src/types.rs index 52bf56a2..05430052 100644 --- a/src/types/mod.rs +++ b/src/types.rs @@ -190,5 +190,10 @@ mod passport_data; mod passport_element_error; mod passport_file; -pub use non_telegram_types::*; -mod non_telegram_types; +pub use non_telegram_types::{country_code::*, currency::*, non_strict_vec::*}; +mod non_telegram_types { + pub(super) mod country_code; + pub(super) mod currency; + pub(crate) mod mime; + pub(super) mod non_strict_vec; +} diff --git a/src/types/non_telegram_types/mod.rs b/src/types/non_telegram_types/mod.rs deleted file mode 100644 index 49360c4f..00000000 --- a/src/types/non_telegram_types/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub use country_code::*; -pub use currency::*; -pub use non_strict_vec::*; - -mod country_code; -mod currency; -mod non_strict_vec; - -pub(crate) mod mime; From 1f4d29c547ccb61e9d0958079dd5cb82e62ff502 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 23 Jan 2021 15:59:59 +0300 Subject: [PATCH 200/755] Add note that serde_multipart is awful --- src/serde_multipart/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/serde_multipart/mod.rs b/src/serde_multipart/mod.rs index 814dc906..dcee7ef1 100644 --- a/src/serde_multipart/mod.rs +++ b/src/serde_multipart/mod.rs @@ -6,6 +6,9 @@ //! ## How it works //! //! You better not know... +//! +//! This whole module is an awful hack and we'll probably stop using it in next +//! versions (in favor of something less automatic, but more simple). mod serializers; mod unserializers; From b16006ec9cc1d35389ede2571370a25d8d5d5156 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 26 Jan 2021 11:56:42 +0300 Subject: [PATCH 201/755] document tls features --- src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index cca00141..77c18e96 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,9 @@ //! //! ## Cargo features //! +//! - `native-tls` = use [`native-tls`] tls implementation (**enabled by +//! default**) +//! - `rustls` — use [`rustls`] tls implementation //! - `auto_send` — enables [`AutoSend`] bot adaptor //! - `throttle` — enables [`Throttle`] bot adaptor //! - `cache_me` — enables [`CacheMe`] bot adaptor @@ -51,6 +54,8 @@ //! [`AutoSend`]: adaptors::AutoSend //! [`Throttle`]: adaptors::Throttle //! [`CacheMe`]: adaptors::CacheMe +//! [`native-tls`]: https://docs.rs/native-tls +//! [`rustls`]: https://docs.rs/rustls #![doc( // FIXME(waffle): use github From 5b328105fa306be3dcb803451cd802a615af4c52 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 26 Jan 2021 13:20:08 +0300 Subject: [PATCH 202/755] Sticker related fixes - Rename `sticker_type.rs` => `input_sticker.rs` (after the contained type) - Make `create_new_sticker_set` multipart - Replace CreateNewStickerSet::{png,tgs}_sticker with `<_>::sticker` - Fix GetStickerSet return type - Revert some previous InputSticker changes: make the variants tuple structsm remove constructors --- src/bot/api.rs | 5 ++-- src/local_macros.rs | 4 +-- src/payloads/create_new_sticker_set.rs | 13 +++++---- src/payloads/get_sticker_set.rs | 4 +-- src/requests/requester.rs | 1 + src/types.rs | 4 +-- .../{sticker_type.rs => input_sticker.rs} | 27 +++---------------- 7 files changed, 20 insertions(+), 38 deletions(-) rename src/types/{sticker_type.rs => input_sticker.rs} (64%) diff --git a/src/bot/api.rs b/src/bot/api.rs index e96f9216..86997994 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -765,13 +765,14 @@ impl Requester for Bot { ) } - type CreateNewStickerSet = JsonRequest; + type CreateNewStickerSet = MultipartRequest; fn create_new_sticker_set( &self, user_id: i32, name: N, title: T, + sticker: InputSticker, emojis: E, ) -> Self::CreateNewStickerSet where @@ -781,7 +782,7 @@ impl Requester for Bot { { Self::CreateNewStickerSet::new( self.clone(), - payloads::CreateNewStickerSet::new(user_id, name, title, emojis), + payloads::CreateNewStickerSet::new(user_id, name, title, sticker, emojis), ) } diff --git a/src/local_macros.rs b/src/local_macros.rs index fb528f5d..a2549a00 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -924,11 +924,11 @@ macro_rules! requester_forward { (@method create_new_sticker_set $body:ident $ty:ident) => { type CreateNewStickerSet = $ty![CreateNewStickerSet]; - fn create_new_sticker_set(&self, user_id: i32, name: N, title: T, emojis: E) -> Self::CreateNewStickerSet where N: Into, + fn create_new_sticker_set(&self, user_id: i32, name: N, title: T, sticker: InputSticker, emojis: E) -> Self::CreateNewStickerSet where N: Into, T: Into, E: Into { let this = self; - $body!(create_new_sticker_set this (user_id: i32, name: N, title: T, emojis: E)) + $body!(create_new_sticker_set this (user_id: i32, name: N, title: T, sticker: InputSticker, emojis: E)) } }; (@method add_sticker_to_set $body:ident $ty:ident) => { diff --git a/src/payloads/create_new_sticker_set.rs b/src/payloads/create_new_sticker_set.rs index 2504ce00..ce2fc4bb 100644 --- a/src/payloads/create_new_sticker_set.rs +++ b/src/payloads/create_new_sticker_set.rs @@ -3,7 +3,7 @@ // edit `cg` instead. use serde::Serialize; -use crate::types::{InputFile, MaskPosition, True}; +use crate::types::{InputSticker, MaskPosition, True}; impl_payload! { /// Use this method to create a new sticker set owned by a user. The bot will be able to edit the sticker set thus created. You must use exactly one of the fields _png\_sticker_ or _tgs\_sticker_. Returns _True_ on success. @@ -16,16 +16,15 @@ impl_payload! { pub name: String [into], /// Sticker set title, 1-64 characters pub title: String [into], + /// **PNG** or **TGS** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a _file\_id_ as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. [More info on Sending Files Âģ] + /// + /// [More info on Sending Files Âģ]: crate::types::InputFile + #[serde(flatten)] + pub sticker: InputSticker, /// One or more emoji corresponding to the sticker pub emojis: String [into], } optional { - /// **PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a _file\_id_ as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. [More info on Sending Files Âģ] - /// - /// [More info on Sending Files Âģ]: crate::types::InputFile - pub png_sticker: InputFile, - /// **TGS** animation with the sticker, uploaded using multipart/form-data. See https://core.telegram.org/animated_stickers#technical-requirements for technical requirements - pub tgs_sticker: InputFile, /// Pass _True_, if a set of mask stickers should be created pub contains_masks: bool, /// A JSON-serialized object for position where the mask should be placed on faces diff --git a/src/payloads/get_sticker_set.rs b/src/payloads/get_sticker_set.rs index a1b74857..96c96f7b 100644 --- a/src/payloads/get_sticker_set.rs +++ b/src/payloads/get_sticker_set.rs @@ -3,12 +3,12 @@ // edit `cg` instead. use serde::Serialize; -use crate::types::True; +use crate::types::StickerSet; impl_payload! { /// Use this method to get a sticker set. On success, a StickerSet object is returned. #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] - pub GetStickerSet (GetStickerSetSetters) => True { + pub GetStickerSet (GetStickerSetSetters) => StickerSet { required { /// Name of the sticker set pub name: String [into], diff --git a/src/requests/requester.rs b/src/requests/requester.rs index e8a32943..b8afc2e4 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -617,6 +617,7 @@ pub trait Requester { user_id: i32, name: N, title: T, + sticker: InputSticker, emojis: E, ) -> Self::CreateNewStickerSet where diff --git a/src/types.rs b/src/types.rs index 05430052..d352caac 100644 --- a/src/types.rs +++ b/src/types.rs @@ -50,6 +50,7 @@ pub use inline_query_result_voice::*; pub use input_file::*; pub use input_media::*; pub use input_message_content::*; +pub use input_sticker::*; pub use invoice::*; pub use keyboard_button::*; pub use keyboard_button_poll_type::*; @@ -80,7 +81,6 @@ pub use shipping_option::*; pub use shipping_query::*; pub use sticker::*; pub use sticker_set::*; -pub use sticker_type::*; pub use successful_payment::*; pub use target_message::*; pub use unit_false::*; @@ -120,6 +120,7 @@ mod inline_keyboard_markup; mod input_file; mod input_media; mod input_message_content; +mod input_sticker; mod invoice; mod keyboard_button; mod keyboard_button_poll_type; @@ -147,7 +148,6 @@ mod shipping_option; mod shipping_query; mod sticker; mod sticker_set; -mod sticker_type; mod successful_payment; mod target_message; mod unit_false; diff --git a/src/types/sticker_type.rs b/src/types/input_sticker.rs similarity index 64% rename from src/types/sticker_type.rs rename to src/types/input_sticker.rs index d15a4812..8f765495 100644 --- a/src/types/sticker_type.rs +++ b/src/types/input_sticker.rs @@ -4,7 +4,6 @@ use crate::types::InputFile; /// Sticker file that may be uploaded to telegram. #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)] -#[serde(untagged)] pub enum InputSticker { /// PNG image with the sticker, must be up to 512 kilobytes in size, /// dimensions must not exceed 512px, and either width or height must be @@ -20,30 +19,12 @@ pub enum InputSticker { /// [`InputFile::FileId`]: crate::types::InputFile::FileId /// /// [More info on Sending Files Âģ]: https://core.telegram.org/bots/api#sending-files - Png { png_sticker: InputFile }, + #[serde(rename = "png_sticker")] + Png(InputFile), /// TGS animation with the sticker, uploaded using multipart/form-data. /// /// See https://core.telegram.org/animated_stickers#technical-requirements for technical requirements - Tgs { tgs_sticker: InputFile }, -} - -impl InputSticker { - /// Create png-`InputSticker`. - /// - /// See [`InputSticker::Png`] for more - /// - /// [`InputSticker::Png`]: crate::types::InputSticker::Png - pub fn png(png_sticker: InputFile) -> Self { - Self::Png { png_sticker } - } - - /// Create tgs-`InputSticker`. - /// - /// See [`InputSticker::Tgs`] for more - /// - /// [`InputSticker::Tgs`]: crate::types::InputSticker::Tgs - pub fn tgs(tgs_sticker: InputFile) -> Self { - Self::Tgs { tgs_sticker } - } + #[serde(rename = "tgs_sticker")] + Tgs(InputFile), } From bfef7f3c5d45a366ab3aa1054e6543d1198791c5 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 26 Jan 2021 13:43:34 +0300 Subject: [PATCH 203/755] fix `self_info` example --- examples/self_info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/self_info.rs b/examples/self_info.rs index 78678e9f..78b2cec4 100644 --- a/examples/self_info.rs +++ b/examples/self_info.rs @@ -15,7 +15,7 @@ async fn main() -> Result<(), Box> { let me = bot.get_me().await?; - bot.send_dice(chat_id, DiceEmoji::Dice).await?; + bot.send_dice(chat_id).emoji(DiceEmoji::Dice).await?; bot.send_message(chat_id, format!("Hi, my name is **{}** 👋", me.first_name)) .await?; From a80ff92abac067fa7857c8ce70df41e35745dce0 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 26 Jan 2021 13:58:51 +0300 Subject: [PATCH 204/755] make `get_me` return `Me` --- src/adaptors/cache_me.rs | 26 +++++++++++++------------- src/payloads/get_me.rs | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/adaptors/cache_me.rs b/src/adaptors/cache_me.rs index 539004bd..541c9d42 100644 --- a/src/adaptors/cache_me.rs +++ b/src/adaptors/cache_me.rs @@ -11,7 +11,7 @@ use once_cell::sync::OnceCell; use crate::{ payloads::GetMe, requests::{HasPayload, Request, Requester}, - types::{ChatId, User, *}, + types::{ChatId, Me, *}, }; /// `get_me` cache. @@ -20,7 +20,7 @@ use crate::{ /// response from `get_me` method. pub struct CacheMe { bot: B, - me: Arc>, + me: Arc>, } impl CacheMe { @@ -52,7 +52,7 @@ impl CacheMe { /// /// Note: internally this uses [`Arc::make_mut`] so this will **not** /// clear cache of clones of self. - pub fn clear(&mut self) -> Option { + pub fn clear(&mut self) -> Option { Arc::make_mut(&mut self.me).take() } } @@ -79,7 +79,7 @@ where fn get_me(&self) -> Self::GetMe { match self.me.get() { - Some(user) => CachedMeRequest(Inner::Ready(user.clone()), GetMe::new()), + Some(me) => CachedMeRequest(Inner::Ready(me.clone()), GetMe::new()), None => CachedMeRequest( Inner::Pending(self.bot.get_me(), Arc::clone(&self.me)), GetMe::new(), @@ -132,8 +132,8 @@ download_forward! { pub struct CachedMeRequest>(Inner, GetMe); enum Inner> { - Ready(User), - Pending(R, Arc>), + Ready(Me), + Pending(R, Arc>), } impl Request for CachedMeRequest @@ -146,7 +146,7 @@ where fn send(self) -> Self::Send { let fut = match self.0 { - Inner::Ready(user) => future::Either::Left(ok(user)), + Inner::Ready(me) => future::Either::Left(ok(me)), Inner::Pending(req, cell) => future::Either::Right(Init(req.send(), cell)), }; Send(fut) @@ -154,7 +154,7 @@ where fn send_ref(&self) -> Self::SendRef { let fut = match &self.0 { - Inner::Ready(user) => future::Either::Left(ok(user.clone())), + Inner::Ready(me) => future::Either::Left(ok(me.clone())), Inner::Pending(req, cell) => { future::Either::Right(Init(req.send_ref(), Arc::clone(cell))) } @@ -175,15 +175,15 @@ impl> HasPayload for CachedMeRequest { } } -type ReadyUser = Ready>; +type ReadyMe = Ready>; #[pin_project::pin_project] pub struct Send>( - #[pin] future::Either, Init>, + #[pin] future::Either, Init>, ); impl> Future for Send { - type Output = Result; + type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); @@ -193,11 +193,11 @@ impl> Future for Send { #[pin_project::pin_project] pub struct SendRef>( - #[pin] future::Either, Init>, + #[pin] future::Either, Init>, ); impl> Future for SendRef { - type Output = Result; + type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); diff --git a/src/payloads/get_me.rs b/src/payloads/get_me.rs index 8794d5a6..a47285b7 100644 --- a/src/payloads/get_me.rs +++ b/src/payloads/get_me.rs @@ -3,14 +3,14 @@ // edit `cg` instead. use serde::Serialize; -use crate::types::User; +use crate::types::Me; impl_payload! { /// A simple method for testing your bot's auth token. Requires no parameters. Returns basic information about the bot in form of a [`User`] object. /// /// [`User`]: crate::types::User #[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Serialize)] - pub GetMe (GetMeSetters) => User { + pub GetMe (GetMeSetters) => Me { } } From 8807cd72282bc1a2a6d3932234392b3ca4ac7d8d Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 26 Jan 2021 14:10:37 +0300 Subject: [PATCH 205/755] fix examples (because get_me now returns Me) --- examples/self_info.rs | 4 ++-- src/lib.rs | 7 +++++-- src/requests/request.rs | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/examples/self_info.rs b/examples/self_info.rs index 78b2cec4..5735bbb3 100644 --- a/examples/self_info.rs +++ b/examples/self_info.rs @@ -1,6 +1,6 @@ use teloxide_core::{ prelude::*, - types::{DiceEmoji, ParseMode}, + types::{DiceEmoji, Me, ParseMode}, }; #[tokio::main] @@ -13,7 +13,7 @@ async fn main() -> Result<(), Box> { .parse_mode(ParseMode::MarkdownV2) .auto_send(); - let me = bot.get_me().await?; + let Me { user: me, .. } = bot.get_me().await?; bot.send_dice(chat_id).emoji(DiceEmoji::Dice).await?; bot.send_message(chat_id, format!("Hi, my name is **{}** 👋", me.first_name)) diff --git a/src/lib.rs b/src/lib.rs index cca00141..8c557789 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,8 +25,11 @@ //! let me = bot.get_me().await?; //! //! bot.send_dice(chat_id).emoji(DiceEmoji::Dice).await?; -//! bot.send_message(chat_id, format!("Hi, my name is **{}** 👋", me.first_name)) -//! .await?; +//! bot.send_message( +//! chat_id, +//! format!("Hi, my name is **{}** 👋", me.user.first_name), +//! ) +//! .await?; //! # Ok::<_, Box>(()) }; //! ``` //! diff --git a/src/requests/request.rs b/src/requests/request.rs index 8517daa2..6ea6c5b4 100644 --- a/src/requests/request.rs +++ b/src/requests/request.rs @@ -44,7 +44,7 @@ pub trait Request: HasPayload { /// use teloxide_core::{ /// payloads::GetMe, /// requests::{JsonRequest, Request}, - /// types::User, + /// types::Me, /// Bot, /// }; /// @@ -53,7 +53,7 @@ pub trait Request: HasPayload { /// // Note: it's recommended to `Requester` instead of creating requests directly /// let method = GetMe::new(); /// let request = JsonRequest::new(bot, method); - /// let _: User = request.send().await.unwrap(); + /// let _: Me = request.send().await.unwrap(); /// # }; /// ``` fn send(self) -> Self::Send; From d663879423997eaecec08d8de3ff7566dd582b6e Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 26 Jan 2021 14:52:50 +0300 Subject: [PATCH 206/755] fix example and add --features full to ci --- .github/workflows/ci.yml | 4 ++-- src/adaptors/auto_send.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4790ad17..939b0930 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,9 +20,9 @@ jobs: include: - rust: stable - features: "" + features: "--features full" - rust: beta - features: "" + features: "--features full" - rust: nightly features: "--all-features" diff --git a/src/adaptors/auto_send.rs b/src/adaptors/auto_send.rs index 77ce8e25..3cacee42 100644 --- a/src/adaptors/auto_send.rs +++ b/src/adaptors/auto_send.rs @@ -28,13 +28,13 @@ use crate::{ /// ```rust /// use teloxide_core::{ /// requests::{Requester, RequesterExt}, -/// types::User, +/// types::Me, /// Bot, /// }; /// /// # async { /// let bot = Bot::new("TOKEN").auto_send(); -/// let myself: User = bot.get_me().await?; // No .send()! +/// let myself: Me = bot.get_me().await?; // No .send()! /// # Ok::<_, teloxide_core::RequestError>(()) }; /// ``` pub struct AutoSend { From 393f6ee7a40df6949fefa18009ff542fb7095389 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 26 Jan 2021 15:35:08 +0300 Subject: [PATCH 207/755] Cleanup setters in types::* - Remove useless setters for types which are only returned from telegam - Add `const` on setters (where possible) - Make `Poll::{open_period,close_date}` public --- src/types/animation.rs | 80 --- src/types/audio.rs | 71 --- src/types/callback_query.rs | 68 --- src/types/chat.rs | 114 ----- src/types/chat_permissions.rs | 53 +- src/types/chat_photo.rs | 54 -- src/types/chosen_inline_result.rs | 50 -- src/types/contact.rs | 53 -- src/types/dice.rs | 16 - src/types/document.rs | 56 -- src/types/encrypted_credentials.rs | 39 -- src/types/encrypted_passport_element.rs | 417 --------------- src/types/file.rs | 45 -- src/types/force_reply.rs | 9 +- src/types/game.rs | 63 --- src/types/game_high_score.rs | 25 - src/types/input_media.rs | 41 +- src/types/input_message_content.rs | 8 +- src/types/invoice.rs | 61 --- src/types/keyboard_button.rs | 4 +- src/types/location.rs | 19 - src/types/me.rs | 39 -- src/types/message.rs | 652 ------------------------ src/types/message_entity.rs | 6 +- src/types/order_info.rs | 50 -- src/types/passport_data.rs | 25 - src/types/passport_file.rs | 41 -- src/types/photo_size.rs | 47 -- src/types/poll.rs | 146 +----- src/types/poll_answer.rs | 35 -- src/types/pre_checkout_query.rs | 68 --- src/types/reply_keyboard_remove.rs | 14 +- src/types/shipping_address.rs | 73 --- src/types/shipping_query.rs | 46 -- src/types/sticker.rs | 90 ---- src/types/sticker_set.rs | 59 --- src/types/successful_payment.rs | 72 --- src/types/update.rs | 16 - src/types/user.rs | 59 --- src/types/user_profile_photos.rs | 27 - src/types/venue.rs | 48 -- src/types/video.rs | 71 --- src/types/video_note.rs | 53 -- src/types/voice.rs | 47 -- src/types/webhook_info.rs | 63 --- 45 files changed, 98 insertions(+), 3095 deletions(-) diff --git a/src/types/animation.rs b/src/types/animation.rs index ae0c9de5..e057aad5 100644 --- a/src/types/animation.rs +++ b/src/types/animation.rs @@ -41,86 +41,6 @@ pub struct Animation { pub file_size: Option, } -impl Animation { - pub fn new( - file_id: S1, - file_unique_id: S2, - width: u32, - height: u32, - duration: u32, - ) -> Self - where - S1: Into, - S2: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - width, - height, - duration, - thumb: None, - file_name: None, - mime_type: None, - file_size: None, - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn width(mut self, val: u32) -> Self { - self.width = val; - self - } - - pub fn height(mut self, val: u32) -> Self { - self.height = val; - self - } - - pub fn duration(mut self, val: u32) -> Self { - self.duration = val; - self - } - - pub fn thumb(mut self, val: PhotoSize) -> Self { - self.thumb = Some(val); - self - } - - pub fn file_name(mut self, val: S) -> Self - where - S: Into, - { - self.file_name = Some(val.into()); - self - } - - pub fn mime_type(mut self, val: Mime) -> Self { - self.mime_type = Some(val); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/types/audio.rs b/src/types/audio.rs index 23225eba..1bf3174f 100644 --- a/src/types/audio.rs +++ b/src/types/audio.rs @@ -38,77 +38,6 @@ pub struct Audio { pub thumb: Option, } -impl Audio { - pub fn new(file_id: S1, file_unique_id: S2, duration: u32) -> Self - where - S1: Into, - S2: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - duration, - performer: None, - title: None, - mime_type: None, - file_size: None, - thumb: None, - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn duration(mut self, val: u32) -> Self { - self.duration = val; - self - } - - pub fn performer(mut self, val: S) -> Self - where - S: Into, - { - self.performer = Some(val.into()); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = Some(val.into()); - self - } - - pub fn mime_type(mut self, val: Mime) -> Self { - self.mime_type = Some(val); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } - - pub fn thumb(mut self, val: PhotoSize) -> Self { - self.thumb = Some(val); - self - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/types/callback_query.rs b/src/types/callback_query.rs index 611c23c1..47cedb96 100644 --- a/src/types/callback_query.rs +++ b/src/types/callback_query.rs @@ -49,74 +49,6 @@ pub struct CallbackQuery { pub game_short_name: Option, } -impl CallbackQuery { - pub fn new(id: S1, from: User, chat_instance: S2) -> Self - where - S1: Into, - S2: Into, - { - Self { - id: id.into(), - from, - message: None, - inline_message_id: None, - chat_instance: chat_instance.into(), - data: None, - game_short_name: None, - } - } - - pub fn id(mut self, id: S) -> Self - where - S: Into, - { - self.id = id.into(); - self - } - - pub fn from(mut self, val: User) -> Self { - self.from = val; - self - } - - pub fn message(mut self, val: Message) -> Self { - self.message = Some(val); - self - } - - pub fn inline_message_id(mut self, val: S) -> Self - where - S: Into, - { - self.inline_message_id = Some(val.into()); - self - } - - pub fn chat_instance(mut self, val: S) -> Self - where - S: Into, - { - self.chat_instance = val.into(); - self - } - - pub fn data(mut self, val: S) -> Self - where - S: Into, - { - self.data = Some(val.into()); - self - } - - pub fn game_short_name(mut self, val: S) -> Self - where - S: Into, - { - self.game_short_name = Some(val.into()); - self - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/types/chat.rs b/src/types/chat.rs index 3ac14786..d0ce754e 100644 --- a/src/types/chat.rs +++ b/src/types/chat.rs @@ -24,31 +24,6 @@ pub struct Chat { pub photo: Option, } -impl Chat { - pub fn new(id: i64, kind: ChatKind) -> Self { - Self { - id, - kind, - photo: None, - } - } - - pub fn id(mut self, val: i64) -> Self { - self.id = val; - self - } - - pub fn kind(mut self, val: ChatKind) -> Self { - self.kind = val; - self - } - - pub fn photo(mut self, val: ChatPhoto) -> Self { - self.photo = Some(val); - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(untagged)] @@ -91,47 +66,6 @@ pub struct ChatPublic { pub pinned_message: Option>, } -impl ChatPublic { - pub fn new(kind: PublicChatKind) -> Self { - Self { - title: None, - kind, - description: None, - invite_link: None, - pinned_message: None, - } - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = Some(val.into()); - self - } - - pub fn description(mut self, val: S) -> Self - where - S: Into, - { - self.description = Some(val.into()); - self - } - - pub fn invite_link(mut self, val: S) -> Self - where - S: Into, - { - self.invite_link = Some(val.into()); - self - } - - pub fn pinned_message(mut self, val: Message) -> Self { - self.pinned_message = Some(Box::new(val)); - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct ChatPrivate { @@ -152,36 +86,6 @@ pub struct ChatPrivate { pub last_name: Option, } -impl ChatPrivate { - pub fn new() -> Self { - Self::default() - } - - pub fn username(mut self, val: S) -> Self - where - S: Into, - { - self.username = Some(val.into()); - self - } - - pub fn first_name(mut self, val: S) -> Self - where - S: Into, - { - self.first_name = Some(val.into()); - self - } - - pub fn last_name(mut self, val: S) -> Self - where - S: Into, - { - self.last_name = Some(val.into()); - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -199,12 +103,6 @@ pub struct PublicChatChannel { pub username: Option, } -impl PublicChatChannel { - pub fn new() -> Self { - Self::default() - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Default, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct PublicChatGroup { @@ -215,12 +113,6 @@ pub struct PublicChatGroup { pub permissions: Option, } -impl PublicChatGroup { - pub fn new() -> Self { - Self::default() - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Default, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct PublicChatSupergroup { @@ -253,12 +145,6 @@ pub struct PublicChatSupergroup { pub slow_mode_delay: Option, } -impl PublicChatSupergroup { - pub fn new() -> Self { - Self::default() - } -} - struct PrivateChatKindVisitor; impl<'de> serde::de::Visitor<'de> for PrivateChatKindVisitor { diff --git a/src/types/chat_permissions.rs b/src/types/chat_permissions.rs index 139f261e..d95b6643 100644 --- a/src/types/chat_permissions.rs +++ b/src/types/chat_permissions.rs @@ -41,7 +41,56 @@ pub struct ChatPermissions { } impl ChatPermissions { - pub fn new() -> Self { - Self::default() + pub const fn new() -> 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, + } + } + + pub const fn can_send_messages(mut self, val: bool) -> Self { + self.can_send_messages = Some(val); + self + } + + pub const fn can_send_media_messages(mut self, val: bool) -> Self { + self.can_send_media_messages = Some(val); + self + } + + pub const fn can_send_polls(mut self, val: bool) -> Self { + self.can_send_polls = Some(val); + self + } + + pub const fn can_send_other_messages(mut self, val: bool) -> Self { + self.can_send_other_messages = Some(val); + self + } + + pub const fn can_add_web_page_previews(mut self, val: bool) -> Self { + self.can_add_web_page_previews = Some(val); + self + } + + pub const fn can_change_info(mut self, val: bool) -> Self { + self.can_change_info = Some(val); + self + } + + pub const fn can_invite_users(mut self, val: bool) -> Self { + self.can_invite_users = Some(val); + self + } + + pub const fn can_pin_messages(mut self, val: bool) -> Self { + self.can_pin_messages = Some(val); + self } } diff --git a/src/types/chat_photo.rs b/src/types/chat_photo.rs index dc19be54..3ebd2bb6 100644 --- a/src/types/chat_photo.rs +++ b/src/types/chat_photo.rs @@ -25,57 +25,3 @@ pub struct ChatPhoto { /// download or reuse the file. pub big_file_unique_id: String, } - -impl ChatPhoto { - pub fn new( - small_file_id: S1, - small_file_unique_id: S2, - big_file_id: S3, - big_file_unique_id: S4, - ) -> Self - where - S1: Into, - S2: Into, - S3: Into, - S4: Into, - { - Self { - small_file_id: small_file_id.into(), - small_file_unique_id: small_file_unique_id.into(), - big_file_id: big_file_id.into(), - big_file_unique_id: big_file_unique_id.into(), - } - } - - pub fn small_file_id(mut self, val: S) -> Self - where - S: Into, - { - self.small_file_id = val.into(); - self - } - - pub fn small_file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.small_file_unique_id = val.into(); - self - } - - pub fn big_file_id(mut self, val: S) -> Self - where - S: Into, - { - self.big_file_id = val.into(); - self - } - - pub fn big_file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.big_file_unique_id = val.into(); - self - } -} diff --git a/src/types/chosen_inline_result.rs b/src/types/chosen_inline_result.rs index b3aed6ee..458ec465 100644 --- a/src/types/chosen_inline_result.rs +++ b/src/types/chosen_inline_result.rs @@ -32,53 +32,3 @@ pub struct ChosenInlineResult { /// The query that was used to obtain the result. pub query: String, } - -impl ChosenInlineResult { - pub fn new(result_id: S1, from: User, query: S2) -> Self - where - S1: Into, - S2: Into, - { - Self { - result_id: result_id.into(), - from, - location: None, - inline_message_id: None, - query: query.into(), - } - } - - pub fn result_id(mut self, val: S) -> Self - where - S: Into, - { - self.result_id = val.into(); - self - } - - pub fn from(mut self, val: User) -> Self { - self.from = val; - self - } - - pub fn location(mut self, val: Location) -> Self { - self.location = val.into(); - self - } - - pub fn inline_message_id(mut self, val: S) -> Self - where - S: Into, - { - self.inline_message_id = Some(val.into()); - self - } - - pub fn query(mut self, val: S) -> Self - where - S: Into, - { - self.query = val.into(); - self - } -} diff --git a/src/types/contact.rs b/src/types/contact.rs index 74661c50..147d31dd 100644 --- a/src/types/contact.rs +++ b/src/types/contact.rs @@ -23,56 +23,3 @@ pub struct Contact { /// [vCard]: https://en.wikipedia.org/wiki/VCard pub vcard: Option, } - -impl Contact { - pub fn new(phone_number: S1, first_name: S2) -> Self - where - S1: Into, - S2: Into, - { - Self { - phone_number: phone_number.into(), - first_name: first_name.into(), - last_name: None, - user_id: None, - vcard: None, - } - } - - pub fn phone_number(mut self, val: S) -> Self - where - S: Into, - { - self.phone_number = val.into(); - self - } - - pub fn first_name(mut self, val: S) -> Self - where - S: Into, - { - self.first_name = val.into(); - self - } - - pub fn last_name(mut self, val: S) -> Self - where - S: Into, - { - self.last_name = Some(val.into()); - self - } - - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = Some(val); - self - } - - pub fn vcard(mut self, val: S) -> Self - where - S: Into, - { - self.vcard = Some(val.into()); - self - } -} diff --git a/src/types/dice.rs b/src/types/dice.rs index c06c5050..404abd8e 100644 --- a/src/types/dice.rs +++ b/src/types/dice.rs @@ -19,19 +19,3 @@ pub struct Dice { /// [`DiceEmoji::Basketball`]:crate::types::DiceEmoji::Basketball value: i32, } - -impl Dice { - pub fn new(emoji: DiceEmoji, value: i32) -> Self { - Self { emoji, value } - } - - pub fn emoji(mut self, val: DiceEmoji) -> Self { - self.emoji = val; - self - } - - pub fn value(mut self, val: i32) -> Self { - self.value = val; - self - } -} diff --git a/src/types/document.rs b/src/types/document.rs index 75e28918..3ea3c861 100644 --- a/src/types/document.rs +++ b/src/types/document.rs @@ -35,59 +35,3 @@ pub struct Document { /// A size of a file. pub file_size: Option, } - -impl Document { - pub fn new(file_id: S1, file_unique_id: S2) -> Self - where - S1: Into, - S2: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - thumb: None, - file_name: None, - mime_type: None, - file_size: None, - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn thumb(mut self, val: PhotoSize) -> Self { - self.thumb = Some(val); - self - } - - pub fn file_name(mut self, val: S) -> Self - where - S: Into, - { - self.file_name = Some(val.into()); - self - } - - pub fn mime_type(mut self, val: Mime) -> Self { - self.mime_type = Some(val); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } -} diff --git a/src/types/encrypted_credentials.rs b/src/types/encrypted_credentials.rs index 5e3562a5..583821e7 100644 --- a/src/types/encrypted_credentials.rs +++ b/src/types/encrypted_credentials.rs @@ -30,45 +30,6 @@ pub struct EncryptedCredentials { pub secret: String, } -impl EncryptedCredentials { - pub fn new(data: S1, hash: S2, secret: S3) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { - data: data.into(), - hash: hash.into(), - secret: secret.into(), - } - } - - pub fn data(mut self, val: S) -> Self - where - S: Into, - { - self.data = val.into(); - self - } - - pub fn hash(mut self, val: S) -> Self - where - S: Into, - { - self.hash = val.into(); - self - } - - pub fn secret(mut self, val: S) -> Self - where - S: Into, - { - self.secret = val.into(); - self - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/types/encrypted_passport_element.rs b/src/types/encrypted_passport_element.rs index 584e9d9a..3d392f57 100644 --- a/src/types/encrypted_passport_element.rs +++ b/src/types/encrypted_passport_element.rs @@ -19,31 +19,6 @@ pub struct EncryptedPassportElement { pub kind: EncryptedPassportElementKind, } -impl EncryptedPassportElement { - pub fn new(hash: S, kind: EncryptedPassportElementKind) -> Self - where - S: Into, - { - Self { - hash: hash.into(), - kind, - } - } - - pub fn hash(mut self, val: S) -> Self - where - S: Into, - { - self.hash = val.into(); - self - } - - pub fn kind(mut self, val: EncryptedPassportElementKind) -> Self { - self.kind = val; - self - } -} - #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] #[allow(clippy::large_enum_variant)] @@ -77,23 +52,6 @@ pub struct EncryptedPassportElementPersonalDetails { pub data: String, } -impl EncryptedPassportElementPersonalDetails { - pub fn new(data: S) -> Self - where - S: Into, - { - Self { data: data.into() } - } - - pub fn data(mut self, val: S) -> Self - where - S: Into, - { - self.data = val.into(); - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct EncryptedPassportElementPassport { @@ -138,46 +96,6 @@ pub struct EncryptedPassportElementPassport { pub translation: Option>, } -impl EncryptedPassportElementPassport { - pub fn new(data: S, front_side: PassportFile, selfie: PassportFile) -> Self - where - S: Into, - { - Self { - data: data.into(), - front_side, - selfie, - translation: None, - } - } - - pub fn data(mut self, val: S) -> Self - where - S: Into, - { - self.data = val.into(); - self - } - - pub fn front_side(mut self, val: PassportFile) -> Self { - self.front_side = val; - self - } - - pub fn selfie(mut self, val: PassportFile) -> Self { - self.selfie = val; - self - } - - pub fn translation

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct EncryptedPassportElementDriverLicense { @@ -231,56 +149,6 @@ pub struct EncryptedPassportElementDriverLicense { pub translation: Option>, } -impl EncryptedPassportElementDriverLicense { - pub fn new( - data: S, - front_side: PassportFile, - reverse_side: PassportFile, - selfie: PassportFile, - ) -> Self - where - S: Into, - { - Self { - data: data.into(), - front_side, - reverse_side, - selfie, - translation: None, - } - } - - pub fn data(mut self, val: S) -> Self - where - S: Into, - { - self.data = val.into(); - self - } - - pub fn front_side(mut self, val: PassportFile) -> Self { - self.front_side = val; - self - } - - pub fn reverse_side(mut self, val: PassportFile) -> Self { - self.reverse_side = val; - self - } - - pub fn selfie(mut self, val: PassportFile) -> Self { - self.selfie = val; - self - } - pub fn translation

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct EncryptedPassportElementIdentityCard { @@ -334,56 +202,6 @@ pub struct EncryptedPassportElementIdentityCard { pub translation: Option>, } -impl EncryptedPassportElementIdentityCard { - pub fn new( - data: S, - front_side: PassportFile, - reverse_side: PassportFile, - selfie: PassportFile, - ) -> Self - where - S: Into, - { - Self { - data: data.into(), - front_side, - reverse_side, - selfie, - translation: None, - } - } - - pub fn data(mut self, val: S) -> Self - where - S: Into, - { - self.data = val.into(); - self - } - - pub fn front_side(mut self, val: PassportFile) -> Self { - self.front_side = val; - self - } - - pub fn reverse_side(mut self, val: PassportFile) -> Self { - self.reverse_side = val; - self - } - - pub fn selfie(mut self, val: PassportFile) -> Self { - self.selfie = val; - self - } - pub fn translation

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct EncryptedPassportElementInternalPassport { @@ -428,46 +246,6 @@ pub struct EncryptedPassportElementInternalPassport { pub translation: Option>, } -impl EncryptedPassportElementInternalPassport { - pub fn new(data: S, front_side: PassportFile, selfie: PassportFile) -> Self - where - S: Into, - { - Self { - data: data.into(), - front_side, - selfie, - translation: None, - } - } - - pub fn data(mut self, val: S) -> Self - where - S: Into, - { - self.data = val.into(); - self - } - - pub fn front_side(mut self, val: PassportFile) -> Self { - self.front_side = val; - self - } - - pub fn selfie(mut self, val: PassportFile) -> Self { - self.selfie = val; - self - } - - pub fn translation

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct EncryptedPassportElementAddress { @@ -482,23 +260,6 @@ pub struct EncryptedPassportElementAddress { pub data: String, } -impl EncryptedPassportElementAddress { - pub fn new(data: S) -> Self - where - S: Into, - { - Self { data: data.into() } - } - - pub fn data(mut self, val: S) -> Self - where - S: Into, - { - self.data = val.into(); - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct EncryptedPassportElementUtilityBill { @@ -525,34 +286,6 @@ pub struct EncryptedPassportElementUtilityBill { pub translation: Option>, } -impl EncryptedPassportElementUtilityBill { - pub fn new(files: F) -> Self - where - F: Into>, - { - Self { - files: files.into(), - translation: None, - } - } - - pub fn files

(mut self, val: P) -> Self - where - P: Into>, - { - self.files = val.into(); - self - } - - pub fn translation

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct EncryptedPassportElementBankStatement { @@ -579,34 +312,6 @@ pub struct EncryptedPassportElementBankStatement { pub translation: Option>, } -impl EncryptedPassportElementBankStatement { - pub fn new(files: F) -> Self - where - F: Into>, - { - Self { - files: files.into(), - translation: None, - } - } - - pub fn files

(mut self, val: P) -> Self - where - P: Into>, - { - self.files = val.into(); - self - } - - pub fn translation

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct EncryptedPassportElementRentalAgreement { @@ -633,34 +338,6 @@ pub struct EncryptedPassportElementRentalAgreement { pub translation: Option>, } -impl EncryptedPassportElementRentalAgreement { - pub fn new(files: F) -> Self - where - F: Into>, - { - Self { - files: files.into(), - translation: None, - } - } - - pub fn files

(mut self, val: P) -> Self - where - P: Into>, - { - self.files = val.into(); - self - } - - pub fn translation

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct EncryptedPassportElementPassportRegistration { @@ -687,34 +364,6 @@ pub struct EncryptedPassportElementPassportRegistration { pub translation: Option>, } -impl EncryptedPassportElementPassportRegistration { - pub fn new(files: F) -> Self - where - F: Into>, - { - Self { - files: files.into(), - translation: None, - } - } - - pub fn files

(mut self, val: P) -> Self - where - P: Into>, - { - self.files = val.into(); - self - } - - pub fn translation

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct EncryptedPassportElementTemporaryRegistration { @@ -741,34 +390,6 @@ pub struct EncryptedPassportElementTemporaryRegistration { pub translation: Option>, } -impl EncryptedPassportElementTemporaryRegistration { - pub fn new(files: F) -> Self - where - F: Into>, - { - Self { - files: files.into(), - translation: None, - } - } - - pub fn files

(mut self, val: P) -> Self - where - P: Into>, - { - self.files = val.into(); - self - } - - pub fn translation

(mut self, val: P) -> Self - where - P: Into>, - { - self.translation = Some(val.into()); - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct EncryptedPassportElementPhoneNumber { @@ -777,47 +398,9 @@ pub struct EncryptedPassportElementPhoneNumber { pub phone_number: String, } -impl EncryptedPassportElementPhoneNumber { - pub fn new(phone_number: S) -> Self - where - S: Into, - { - Self { - phone_number: phone_number.into(), - } - } - - pub fn phone_number(mut self, val: S) -> Self - where - S: Into, - { - self.phone_number = val.into(); - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct EncryptedPassportElementEmail { /// User's verified email address, available only for `email` type. pub email: String, } - -impl EncryptedPassportElementEmail { - pub fn new(email: S) -> Self - where - S: Into, - { - Self { - email: email.into(), - } - } - - pub fn email(mut self, val: S) -> Self - where - S: Into, - { - self.email = val.into(); - self - } -} diff --git a/src/types/file.rs b/src/types/file.rs index a1eeeb75..82aa237e 100644 --- a/src/types/file.rs +++ b/src/types/file.rs @@ -30,48 +30,3 @@ pub struct File { /// crate::net::Download::download_file pub file_path: String, } - -impl File { - pub fn new(file_id: S1, file_unique_id: S2, file_size: u32, file_path: S3) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - file_size, - file_path: file_path.into(), - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = val; - self - } - - pub fn file_path(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } -} diff --git a/src/types/force_reply.rs b/src/types/force_reply.rs index cb8ed997..462e47a5 100644 --- a/src/types/force_reply.rs +++ b/src/types/force_reply.rs @@ -29,11 +29,14 @@ pub struct ForceReply { } impl ForceReply { - pub fn new() -> Self { - Self::default() + pub const fn new() -> Self { + Self { + force_reply: True, + selective: None, + } } - pub fn selective(mut self, val: bool) -> Self { + pub const fn selective(mut self, val: bool) -> Self { self.selective = Some(val); self } diff --git a/src/types/game.rs b/src/types/game.rs index ca3f4e7d..fa9c5f33 100644 --- a/src/types/game.rs +++ b/src/types/game.rs @@ -39,66 +39,3 @@ pub struct Game { /// [@Botfather]: https://t.me/botfather pub animation: Option, } - -impl Game { - pub fn new(title: S1, description: S2, photo: P) -> Self - where - S1: Into, - S2: Into, - P: Into>, - { - Self { - title: title.into(), - description: description.into(), - photo: photo.into(), - text: None, - text_entities: None, - animation: None, - } - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn description(mut self, val: S) -> Self - where - S: Into, - { - self.description = val.into(); - self - } - - pub fn photo

(mut self, val: P) -> Self - where - P: Into>, - { - self.photo = val.into(); - self - } - - pub fn text(mut self, val: S) -> Self - where - S: Into, - { - self.text = Some(val.into()); - self - } - - pub fn text_entities(mut self, val: T) -> Self - where - T: Into>, - { - self.text_entities = Some(val.into()); - self - } - - pub fn animation(mut self, val: Animation) -> Self { - self.animation = Some(val); - self - } -} diff --git a/src/types/game_high_score.rs b/src/types/game_high_score.rs index 57ac5719..059686c1 100644 --- a/src/types/game_high_score.rs +++ b/src/types/game_high_score.rs @@ -16,28 +16,3 @@ pub struct GameHighScore { /// Score. pub score: u32, } - -impl GameHighScore { - pub fn new(position: u32, user: User, score: u32) -> Self { - Self { - position, - user, - score, - } - } - - pub fn position(mut self, val: u32) -> Self { - self.position = val; - self - } - - pub fn user(mut self, val: User) -> Self { - self.user = val; - self - } - - pub fn score(mut self, val: u32) -> Self { - self.score = val; - self - } -} diff --git a/src/types/input_media.rs b/src/types/input_media.rs index a5d3dd84..2cdb4637 100644 --- a/src/types/input_media.rs +++ b/src/types/input_media.rs @@ -38,7 +38,7 @@ pub struct InputMediaPhoto { } impl InputMediaPhoto { - pub fn new(media: InputFile) -> Self { + pub const fn new(media: InputFile) -> Self { Self { media, caption: None, @@ -59,7 +59,7 @@ impl InputMediaPhoto { self } - pub fn parse_mode(mut self, val: ParseMode) -> Self { + pub const fn parse_mode(mut self, val: ParseMode) -> Self { self.parse_mode = Some(val); self } @@ -106,7 +106,7 @@ pub struct InputMediaVideo { } impl InputMediaVideo { - pub fn new(media: InputFile) -> Self { + pub const fn new(media: InputFile) -> Self { Self { media, thumb: None, @@ -137,27 +137,27 @@ impl InputMediaVideo { self } - pub fn parse_mode(mut self, val: ParseMode) -> Self { + pub const fn parse_mode(mut self, val: ParseMode) -> Self { self.parse_mode = Some(val); self } - pub fn width(mut self, val: u16) -> Self { + pub const fn width(mut self, val: u16) -> Self { self.width = Some(val); self } - pub fn height(mut self, val: u16) -> Self { + pub const fn height(mut self, val: u16) -> Self { self.height = Some(val); self } - pub fn duration(mut self, val: u16) -> Self { + pub const fn duration(mut self, val: u16) -> Self { self.duration = Some(val); self } - pub fn supports_streaming(mut self, val: bool) -> Self { + pub const fn supports_streaming(mut self, val: bool) -> Self { self.supports_streaming = Some(val); self } @@ -202,7 +202,7 @@ pub struct InputMediaAnimation { } impl InputMediaAnimation { - pub fn new(media: InputFile) -> Self { + pub const fn new(media: InputFile) -> Self { Self { media, thumb: None, @@ -232,22 +232,22 @@ impl InputMediaAnimation { self } - pub fn parse_mode(mut self, val: ParseMode) -> Self { + pub const fn parse_mode(mut self, val: ParseMode) -> Self { self.parse_mode = Some(val); self } - pub fn width(mut self, val: u16) -> Self { + pub const fn width(mut self, val: u16) -> Self { self.width = Some(val); self } - pub fn height(mut self, val: u16) -> Self { + pub const fn height(mut self, val: u16) -> Self { self.height = Some(val); self } - pub fn duration(mut self, val: u16) -> Self { + pub const fn duration(mut self, val: u16) -> Self { self.duration = Some(val); self } @@ -291,7 +291,7 @@ pub struct InputMediaAudio { } impl InputMediaAudio { - pub fn new(media: InputFile) -> Self { + pub const fn new(media: InputFile) -> Self { Self { media, thumb: None, @@ -321,12 +321,12 @@ impl InputMediaAudio { self } - pub fn parse_mode(mut self, val: ParseMode) -> Self { + pub const fn parse_mode(mut self, val: ParseMode) -> Self { self.parse_mode = Some(val); self } - pub fn duration(mut self, val: u16) -> Self { + pub const fn duration(mut self, val: u16) -> Self { self.duration = Some(val); self } @@ -377,7 +377,7 @@ pub struct InputMediaDocument { } impl InputMediaDocument { - pub fn new(media: InputFile) -> Self { + pub const fn new(media: InputFile) -> Self { Self { media, thumb: None, @@ -386,6 +386,11 @@ impl InputMediaDocument { } } + pub fn media(mut self, val: InputFile) -> Self { + self.media = val; + self + } + pub fn thumb(mut self, val: InputFile) -> Self { self.thumb = Some(val); self @@ -399,7 +404,7 @@ impl InputMediaDocument { self } - pub fn parse_mode(mut self, val: ParseMode) -> Self { + pub const fn parse_mode(mut self, val: ParseMode) -> Self { self.parse_mode = Some(val); self } diff --git a/src/types/input_message_content.rs b/src/types/input_message_content.rs index 169fdd1c..f8cae294 100644 --- a/src/types/input_message_content.rs +++ b/src/types/input_message_content.rs @@ -82,7 +82,7 @@ pub struct InputMessageContentLocation { } impl InputMessageContentLocation { - pub fn new(latitude: f64, longitude: f64) -> Self { + pub const fn new(latitude: f64, longitude: f64) -> Self { Self { latitude, longitude, @@ -90,17 +90,17 @@ impl InputMessageContentLocation { } } - pub fn latitude(mut self, val: f64) -> Self { + pub const fn latitude(mut self, val: f64) -> Self { self.latitude = val; self } - pub fn longitude(mut self, val: f64) -> Self { + pub const fn longitude(mut self, val: f64) -> Self { self.longitude = val; self } - pub fn live_period(mut self, val: u32) -> Self { + pub const fn live_period(mut self, val: u32) -> Self { self.live_period = Some(val); self } diff --git a/src/types/invoice.rs b/src/types/invoice.rs index cb4383e6..b9f2f5b6 100644 --- a/src/types/invoice.rs +++ b/src/types/invoice.rs @@ -27,64 +27,3 @@ pub struct Invoice { /// [`currencies.json`]: https://core.telegram.org/bots/payments/currencies.json pub total_amount: i32, } - -impl Invoice { - pub fn new( - title: S1, - description: S2, - start_parameter: S3, - currency: S4, - total_amount: i32, - ) -> Self - where - S1: Into, - S2: Into, - S3: Into, - S4: Into, - { - Self { - title: title.into(), - description: description.into(), - start_parameter: start_parameter.into(), - currency: currency.into(), - total_amount, - } - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn description(mut self, val: S) -> Self - where - S: Into, - { - self.description = val.into(); - self - } - - pub fn start_parameter(mut self, val: S) -> Self - where - S: Into, - { - self.start_parameter = val.into(); - self - } - - pub fn currency(mut self, val: S) -> Self - where - S: Into, - { - self.currency = val.into(); - self - } - - pub fn total_amount(mut self, val: i32) -> Self { - self.total_amount = val; - self - } -} diff --git a/src/types/keyboard_button.rs b/src/types/keyboard_button.rs index f25f4f52..2cefc957 100644 --- a/src/types/keyboard_button.rs +++ b/src/types/keyboard_button.rs @@ -37,9 +37,9 @@ impl KeyboardButton { pub fn request(mut self, val: T) -> Self where - T: Into>, + T: Into, { - self.request = val.into(); + self.request = Some(val.into()); self } } diff --git a/src/types/location.rs b/src/types/location.rs index 7fd1a71d..c624d638 100644 --- a/src/types/location.rs +++ b/src/types/location.rs @@ -9,22 +9,3 @@ pub struct Location { /// Latitude as defined by sender. pub latitude: f64, } - -impl Location { - pub fn new(longitude: f64, latitude: f64) -> Self { - Self { - longitude, - latitude, - } - } - - pub fn latitude(mut self, val: f64) -> Self { - self.latitude = val; - self - } - - pub fn longitude(mut self, val: f64) -> Self { - self.longitude = val; - self - } -} diff --git a/src/types/me.rs b/src/types/me.rs index 3f7d1c32..ddb699dc 100644 --- a/src/types/me.rs +++ b/src/types/me.rs @@ -20,42 +20,3 @@ pub struct Me { /// `true`, if the bot supports inline queries. pub supports_inline_queries: bool, } - -impl Me { - pub fn new( - user: User, - can_join_groups: bool, - can_read_all_group_messages: bool, - supports_inline_queries: bool, - ) -> Self { - Self { - user, - can_join_groups, - can_read_all_group_messages, - supports_inline_queries, - } - } - - pub fn user(mut self, val: User) -> Self { - self.user = val; - self - } - - #[warn(clippy::wrong_self_convention)] - pub fn can_join_groups(mut self, val: bool) -> Self { - self.can_join_groups = val; - self - } - - #[warn(clippy::wrong_self_convention)] - pub fn can_read_all_group_messages(mut self, val: bool) -> Self { - self.can_read_all_group_messages = val; - self - } - - #[warn(clippy::wrong_self_convention)] - pub fn supports_inline_queries(mut self, val: bool) -> Self { - self.supports_inline_queries = val; - self - } -} diff --git a/src/types/message.rs b/src/types/message.rs index 1e13e158..bc8ddb32 100644 --- a/src/types/message.rs +++ b/src/types/message.rs @@ -31,43 +31,6 @@ pub struct Message { pub kind: MessageKind, } -impl Message { - pub fn new(id: i32, date: i32, chat: Chat, kind: MessageKind) -> Self { - Self { - id, - date, - chat, - kind, - via_bot: None, - } - } - - pub fn id(mut self, val: i32) -> Self { - self.id = val; - self - } - - pub fn date(mut self, val: i32) -> Self { - self.date = val; - self - } - - pub fn chat(mut self, val: Chat) -> Self { - self.chat = val; - self - } - - pub fn kind(mut self, val: MessageKind) -> Self { - self.kind = val; - self - } - - pub fn via_bot(mut self, val: User) -> Self { - self.via_bot = Some(val); - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum MessageKind { @@ -109,43 +72,6 @@ pub struct MessageCommon { pub reply_markup: Option, } -impl MessageCommon { - pub fn new(forward_kind: ForwardKind, media_kind: MediaKind) -> Self { - Self { - from: None, - forward_kind, - edit_date: None, - media_kind, - reply_markup: None, - } - } - - pub fn from(mut self, val: User) -> Self { - self.from = Some(val); - self - } - - pub fn forward_kind(mut self, val: ForwardKind) -> Self { - self.forward_kind = val; - self - } - - pub fn edit_date(mut self, val: i32) -> Self { - self.edit_date = Some(val); - self - } - - pub fn media_kind(mut self, val: MediaKind) -> Self { - self.media_kind = val; - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageNewChatMembers { /// New members that were added to the group or supergroup and @@ -154,25 +80,6 @@ pub struct MessageNewChatMembers { pub new_chat_members: Vec, } -impl MessageNewChatMembers { - pub fn new(new_chat_members: N) -> Self - where - N: Into>, - { - Self { - new_chat_members: new_chat_members.into(), - } - } - - pub fn new_chat_members(mut self, val: N) -> Self - where - N: Into>, - { - self.new_chat_members = val.into(); - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageLeftChatMember { /// A member was removed from the group, information about them (this @@ -180,99 +87,30 @@ pub struct MessageLeftChatMember { pub left_chat_member: User, } -impl MessageLeftChatMember { - pub fn new(left_chat_member: N) -> Self - where - N: Into, - { - Self { - left_chat_member: left_chat_member.into(), - } - } - - pub fn left_chat_member(mut self, val: N) -> Self - where - N: Into, - { - self.left_chat_member = val.into(); - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageNewChatTitle { /// A chat title was changed to this value. pub new_chat_title: String, } -impl MessageNewChatTitle { - pub fn new(new_chat_title: N) -> Self - where - N: Into, - { - Self { - new_chat_title: new_chat_title.into(), - } - } - - pub fn new_chat_title(mut self, val: N) -> Self - where - N: Into, - { - self.new_chat_title = val.into(); - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageNewChatPhoto { /// A chat photo was change to this value. pub new_chat_photo: Vec, } -impl MessageNewChatPhoto { - pub fn new(new_chat_photo: N) -> Self - where - N: Into>, - { - Self { - new_chat_photo: new_chat_photo.into(), - } - } - - pub fn new_chat_photo(mut self, val: N) -> Self - where - N: Into>, - { - self.new_chat_photo = val.into(); - self - } -} - #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct MessageDeleteChatPhoto { /// Service message: the chat photo was deleted. pub delete_chat_photo: True, } -impl MessageDeleteChatPhoto { - pub fn new() -> Self { - Self::default() - } -} - #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct MessageGroupChatCreated { /// Service message: the group has been created. pub group_chat_created: True, } -impl MessageGroupChatCreated { - pub fn new() -> Self { - Self::default() - } -} - #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct MessageSupergroupChatCreated { /// Service message: the supergroup has been created. This field can‘t @@ -283,12 +121,6 @@ pub struct MessageSupergroupChatCreated { pub supergroup_chat_created: True, } -impl MessageSupergroupChatCreated { - pub fn new() -> Self { - Self::default() - } -} - #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct MessageChannelChatCreated { /// Service message: the channel has been created. This field can‘t be @@ -299,12 +131,6 @@ pub struct MessageChannelChatCreated { pub channel_chat_created: True, } -impl MessageChannelChatCreated { - pub fn new() -> Self { - Self::default() - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageMigrate { /// The group has been migrated to a supergroup with the specified @@ -324,25 +150,6 @@ pub struct MessageMigrate { pub migrate_from_chat_id: i64, } -impl MessageMigrate { - pub fn new(migrate_to_chat_id: i64, migrate_from_chat_id: i64) -> Self { - Self { - migrate_to_chat_id, - migrate_from_chat_id, - } - } - - pub fn migrate_to_chat_id(mut self, val: i64) -> Self { - self.migrate_to_chat_id = val; - self - } - - pub fn migrate_from_chat_id(mut self, val: i64) -> Self { - self.migrate_from_chat_id = val; - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessagePinned { /// Specified message was pinned. Note that the Message object in this @@ -352,19 +159,6 @@ pub struct MessagePinned { pub pinned: Box, } -impl MessagePinned { - pub fn new(pinned: Message) -> Self { - Self { - pinned: Box::new(pinned), - } - } - - pub fn pinned(mut self, val: Message) -> Self { - self.pinned = Box::new(val); - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageInvoice { /// Message is an invoice for a [payment], information about the @@ -375,17 +169,6 @@ pub struct MessageInvoice { pub invoice: Invoice, } -impl MessageInvoice { - pub fn new(invoice: Invoice) -> Self { - Self { invoice } - } - - pub fn invoice(mut self, val: Invoice) -> Self { - self.invoice = val; - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageSuccessfulPayment { /// Message is a service message about a successful payment, @@ -395,17 +178,6 @@ pub struct MessageSuccessfulPayment { pub successful_payment: SuccessfulPayment, } -impl MessageSuccessfulPayment { - pub fn new(successful_payment: SuccessfulPayment) -> Self { - Self { successful_payment } - } - - pub fn successful_payment(mut self, val: SuccessfulPayment) -> Self { - self.successful_payment = val; - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageConnectedWebsite { /// The domain name of the website on which the user has logged in. @@ -415,42 +187,12 @@ pub struct MessageConnectedWebsite { pub connected_website: String, } -impl MessageConnectedWebsite { - pub fn new(connected_website: S) -> Self - where - S: Into, - { - Self { - connected_website: connected_website.into(), - } - } - - pub fn connected_website(mut self, val: S) -> Self - where - S: Into, - { - self.connected_website = val.into(); - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessagePassportData { /// Telegram Passport data. pub passport_data: PassportData, } -impl MessagePassportData { - pub fn new(passport_data: PassportData) -> Self { - Self { passport_data } - } - - pub fn passport_data(mut self, val: PassportData) -> Self { - self.passport_data = val; - self - } -} - #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum ForwardedFrom { #[serde(rename = "forward_from")] @@ -482,40 +224,6 @@ pub struct ForwardChannel { pub signature: Option, } -impl ForwardChannel { - pub fn new(date: i32, chat: Chat, message_id: i32) -> Self { - Self { - date, - chat, - message_id, - signature: None, - } - } - - pub fn date(mut self, val: i32) -> Self { - self.date = val; - self - } - - pub fn chat(mut self, val: Chat) -> Self { - self.chat = val; - self - } - - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - pub fn signature(mut self, val: S) -> Self - where - S: Into, - { - self.signature = Some(val.into()); - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ForwardNonChannel { #[serde(rename = "forward_date")] @@ -525,38 +233,11 @@ pub struct ForwardNonChannel { pub from: ForwardedFrom, } -impl ForwardNonChannel { - pub fn new(date: i32, from: ForwardedFrom) -> Self { - Self { date, from } - } - - pub fn date(mut self, val: i32) -> Self { - self.date = val; - self - } - - pub fn from(mut self, val: ForwardedFrom) -> Self { - self.from = val; - self - } -} - #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct ForwardOrigin { pub reply_to_message: Option>, } -impl ForwardOrigin { - pub fn new() -> Self { - Self::default() - } - - pub fn reply_to_message(mut self, val: Message) -> Self { - self.reply_to_message = Some(Box::new(val)); - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum MediaKind { @@ -597,41 +278,6 @@ pub struct MediaAnimation { pub caption_entities: Vec, } -impl MediaAnimation { - pub fn new(animation: Animation, caption_entities: CE) -> Self - where - CE: Into>, - { - Self { - animation, - document: (), - caption: None, - caption_entities: caption_entities.into(), - } - } - - pub fn animation(mut self, val: Animation) -> Self { - self.animation = val; - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities(mut self, val: CE) -> Self - where - CE: Into>, - { - self.caption_entities = val.into(); - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MediaAudio { @@ -647,57 +293,12 @@ pub struct MediaAudio { pub caption_entities: Vec, } -impl MediaAudio { - pub fn new(audio: Audio, caption_entities: CE) -> Self - where - CE: Into>, - { - Self { - audio, - caption: None, - caption_entities: caption_entities.into(), - } - } - - pub fn audio(mut self, val: Audio) -> Self { - self.audio = val; - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities(mut self, val: CE) -> Self - where - CE: Into>, - { - self.caption_entities = val.into(); - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MediaContact { /// Message is a shared contact, information about the contact. contact: Contact, } -impl MediaContact { - pub fn new(contact: Contact) -> Self { - Self { contact } - } - - pub fn contact(mut self, val: Contact) -> Self { - self.contact = val; - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MediaDocument { @@ -713,40 +314,6 @@ pub struct MediaDocument { pub caption_entities: Vec, } -impl MediaDocument { - pub fn new(document: Document, caption_entities: CE) -> Self - where - CE: Into>, - { - Self { - document, - caption: None, - caption_entities: caption_entities.into(), - } - } - - pub fn document(mut self, val: Document) -> Self { - self.document = val; - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities(mut self, val: CE) -> Self - where - CE: Into>, - { - self.caption_entities = val.into(); - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MediaGame { /// Message is a game, information about the game. [More @@ -756,34 +323,12 @@ pub struct MediaGame { pub game: Game, } -impl MediaGame { - pub fn new(game: Game) -> Self { - Self { game } - } - - pub fn game(mut self, val: Game) -> Self { - self.game = val; - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MediaLocation { /// Message is a shared location, information about the location. pub location: Location, } -impl MediaLocation { - pub fn new(location: Location) -> Self { - Self { location } - } - - pub fn location(mut self, val: Location) -> Self { - self.location = val; - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MediaPhoto { @@ -803,87 +348,18 @@ pub struct MediaPhoto { pub media_group_id: Option, } -impl MediaPhoto { - pub fn new(photo: P, caption_entities: CE) -> Self - where - P: Into>, - CE: Into>, - { - Self { - photo: photo.into(), - caption: None, - caption_entities: caption_entities.into(), - media_group_id: None, - } - } - - pub fn photo

(mut self, val: P) -> Self - where - P: Into>, - { - self.photo = val.into(); - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities(mut self, val: CE) -> Self - where - CE: Into>, - { - self.caption_entities = val.into(); - self - } - - pub fn media_group_id(mut self, val: S) -> Self - where - S: Into, - { - self.media_group_id = Some(val.into()); - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MediaPoll { /// Message is a native poll, information about the poll. pub poll: Poll, } -impl MediaPoll { - pub fn new(poll: Poll) -> Self { - Self { poll } - } - - pub fn poll(mut self, val: Poll) -> Self { - self.poll = val; - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MediaSticker { /// Message is a sticker, information about the sticker. pub sticker: Sticker, } -impl MediaSticker { - pub fn new(sticker: Sticker) -> Self { - Self { sticker } - } - - pub fn poll(mut self, val: Sticker) -> Self { - self.sticker = val; - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MediaText { /// For text messages, the actual UTF-8 text of the message, 0-4096 @@ -896,35 +372,6 @@ pub struct MediaText { pub entities: Vec, } -impl MediaText { - pub fn new(text: S, entities: E) -> Self - where - S: Into, - E: Into>, - { - Self { - text: text.into(), - entities: entities.into(), - } - } - - pub fn text(mut self, val: S) -> Self - where - S: Into, - { - self.text = val.into(); - self - } - - pub fn entities(mut self, val: CE) -> Self - where - CE: Into>, - { - self.entities = val.into(); - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MediaVideo { @@ -944,49 +391,6 @@ pub struct MediaVideo { pub media_group_id: Option, } -impl MediaVideo { - pub fn new(video: Video, caption_entities: CE) -> Self - where - CE: Into>, - { - Self { - video, - caption: None, - caption_entities: caption_entities.into(), - media_group_id: None, - } - } - - pub fn video(mut self, val: Video) -> Self { - self.video = val; - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities(mut self, val: CE) -> Self - where - CE: Into>, - { - self.caption_entities = val.into(); - self - } - - pub fn media_group_id(mut self, val: S) -> Self - where - S: Into, - { - self.media_group_id = Some(val.into()); - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MediaVideoNote { /// Message is a [video note], information about the video message. @@ -995,17 +399,6 @@ pub struct MediaVideoNote { pub video_note: VideoNote, } -impl MediaVideoNote { - pub fn new(video_note: VideoNote) -> Self { - Self { video_note } - } - - pub fn video_note(mut self, val: VideoNote) -> Self { - self.video_note = val; - self - } -} - #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MediaVoice { @@ -1021,57 +414,12 @@ pub struct MediaVoice { pub caption_entities: Vec, } -impl MediaVoice { - pub fn new(voice: Voice, caption_entities: CE) -> Self - where - CE: Into>, - { - Self { - voice, - caption: None, - caption_entities: caption_entities.into(), - } - } - - pub fn voice(mut self, val: Voice) -> Self { - self.voice = val; - self - } - - pub fn caption(mut self, val: S) -> Self - where - S: Into, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities(mut self, val: CE) -> Self - where - CE: Into>, - { - self.caption_entities = val.into(); - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MediaVenue { /// Message is a venue, information about the venue. pub venue: Venue, } -impl MediaVenue { - pub fn new(venue: Venue) -> Self { - Self { venue } - } - - pub fn venue(mut self, val: Venue) -> Self { - self.venue = val; - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageDice { /// Message is a dice with random value from 1 to 6. diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs index 79876cf0..fbd57216 100644 --- a/src/types/message_entity.rs +++ b/src/types/message_entity.rs @@ -20,7 +20,7 @@ pub struct MessageEntity { } impl MessageEntity { - pub fn new(kind: MessageEntityKind, offset: usize, length: usize) -> Self { + pub const fn new(kind: MessageEntityKind, offset: usize, length: usize) -> Self { Self { kind, offset, @@ -33,12 +33,12 @@ impl MessageEntity { self } - pub fn offset(mut self, val: usize) -> Self { + pub const fn offset(mut self, val: usize) -> Self { self.offset = val; self } - pub fn length(mut self, val: usize) -> Self { + pub const fn length(mut self, val: usize) -> Self { self.length = val; self } diff --git a/src/types/order_info.rs b/src/types/order_info.rs index 5dca09d7..e5b35bbc 100644 --- a/src/types/order_info.rs +++ b/src/types/order_info.rs @@ -19,53 +19,3 @@ pub struct OrderInfo { /// User's shipping address. pub shipping_address: ShippingAddress, } - -impl OrderInfo { - pub fn new( - name: S1, - phone_number: S2, - email: S3, - shipping_address: ShippingAddress, - ) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { - name: name.into(), - phone_number: phone_number.into(), - email: email.into(), - shipping_address, - } - } - - pub fn name(mut self, val: S) -> Self - where - S: Into, - { - self.name = val.into(); - self - } - - pub fn phone_number(mut self, val: S) -> Self - where - S: Into, - { - self.phone_number = val.into(); - self - } - - pub fn email(mut self, val: S) -> Self - where - S: Into, - { - self.email = val.into(); - self - } - - pub fn shipping_address(mut self, val: ShippingAddress) -> Self { - self.shipping_address = val; - self - } -} diff --git a/src/types/passport_data.rs b/src/types/passport_data.rs index c34f97ea..5e548f57 100644 --- a/src/types/passport_data.rs +++ b/src/types/passport_data.rs @@ -15,28 +15,3 @@ pub struct PassportData { /// Encrypted credentials required to decrypt the data. pub credentials: EncryptedCredentials, } - -impl PassportData { - pub fn new(data: E, credentials: EncryptedCredentials) -> Self - where - E: Into>, - { - Self { - data: data.into(), - credentials, - } - } - - pub fn data(mut self, val: E) -> Self - where - E: Into>, - { - self.data = val.into(); - self - } - - pub fn credentials(mut self, val: EncryptedCredentials) -> Self { - self.credentials = val; - self - } -} diff --git a/src/types/passport_file.rs b/src/types/passport_file.rs index f5c68741..18f1a5d4 100644 --- a/src/types/passport_file.rs +++ b/src/types/passport_file.rs @@ -22,44 +22,3 @@ pub struct PassportFile { /// Unix time when the file was uploaded. pub file_date: u64, } - -impl PassportFile { - pub fn new(file_id: S1, file_unique_id: S2, file_size: u64, file_date: u64) -> Self - where - S1: Into, - S2: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - file_size, - file_date, - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn file_size(mut self, val: u64) -> Self { - self.file_size = val; - self - } - - pub fn file_date(mut self, val: u64) -> Self { - self.file_date = val; - self - } -} diff --git a/src/types/photo_size.rs b/src/types/photo_size.rs index 91e05639..44ddfe32 100644 --- a/src/types/photo_size.rs +++ b/src/types/photo_size.rs @@ -25,53 +25,6 @@ pub struct PhotoSize { pub file_size: Option, } -impl PhotoSize { - pub fn new(file_id: S1, file_unique_id: S2, width: i32, height: i32) -> Self - where - S1: Into, - S2: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - width, - height, - file_size: None, - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn width(mut self, val: i32) -> Self { - self.width = val; - self - } - - pub fn height(mut self, val: i32) -> Self { - self.height = val; - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/types/poll.rs b/src/types/poll.rs index 8176aa5b..23bb9409 100644 --- a/src/types/poll.rs +++ b/src/types/poll.rs @@ -46,128 +46,11 @@ pub struct Poll { pub explanation_entities: Option>, /// Amount of time in seconds the poll will be active after creation. - open_period: Option, + pub open_period: Option, /// Point in time (Unix timestamp) when the poll will be automatically /// closed. - close_date: Option, -} - -impl Poll { - #[allow(clippy::too_many_arguments)] - pub fn new( - id: S1, - question: S2, - options: O, - is_closed: bool, - total_voter_count: i32, - is_anonymous: bool, - poll_type: PollType, - allows_multiple_answers: bool, - ) -> Self - where - S1: Into, - S2: Into, - O: Into>, - { - Self { - id: id.into(), - question: question.into(), - options: options.into(), - is_closed, - total_voter_count, - is_anonymous, - poll_type, - allows_multiple_answers, - correct_option_id: None, - explanation: None, - explanation_entities: None, - open_period: None, - close_date: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn question(mut self, val: S) -> Self - where - S: Into, - { - self.question = val.into(); - self - } - - pub fn options

(mut self, val: P) -> Self - where - P: Into>, - { - self.options = val.into(); - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_closed(mut self, val: bool) -> Self { - self.is_closed = val; - self - } - - pub fn total_voter_count(mut self, val: i32) -> Self { - self.total_voter_count = val; - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_anonymous(mut self, val: bool) -> Self { - self.is_anonymous = val; - self - } - - pub fn poll_type(mut self, val: PollType) -> Self { - self.poll_type = val; - self - } - - pub fn allows_multiple_answers(mut self, val: bool) -> Self { - self.allows_multiple_answers = val; - self - } - - pub fn correct_option_id(mut self, val: i32) -> Self { - self.correct_option_id = Some(val); - self - } - - pub fn explanation(mut self, val: S) -> Self - where - S: Into, - { - self.explanation = Some(val.into()); - self - } - - pub fn explanation_entities(mut self, val: S) -> Self - where - S: Into>, - { - self.explanation_entities = Some(val.into()); - self - } - - pub fn open_period(mut self, val: i32) -> Self { - self.open_period = Some(val); - self - } - - pub fn close_date(mut self, val: i32) -> Self { - self.close_date = Some(val); - self - } + pub close_date: Option, } /// This object contains information about one answer option in a poll. @@ -182,31 +65,6 @@ pub struct PollOption { pub voter_count: i32, } -impl PollOption { - pub fn new(text: S, voter_count: i32) -> Self - where - S: Into, - { - Self { - text: text.into(), - voter_count, - } - } - - pub fn text(mut self, val: S) -> Self - where - S: Into, - { - self.text = val.into(); - self - } - - pub fn voter_count(mut self, val: i32) -> Self { - self.voter_count = val; - self - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/types/poll_answer.rs b/src/types/poll_answer.rs index befeb420..b8f26968 100644 --- a/src/types/poll_answer.rs +++ b/src/types/poll_answer.rs @@ -14,38 +14,3 @@ pub struct PollAnswer { /// May be empty if the user retracted their vote. pub option_ids: Vec, } - -impl PollAnswer { - pub fn new(poll_id: S, user: User, option_ids: O) -> Self - where - S: Into, - O: Into>, - { - Self { - poll_id: poll_id.into(), - user, - option_ids: option_ids.into(), - } - } - - pub fn poll_id(mut self, val: S) -> Self - where - S: Into, - { - self.poll_id = val.into(); - self - } - - pub fn user(mut self, val: User) -> Self { - self.user = val; - self - } - - pub fn option_ids(mut self, val: S) -> Self - where - S: Into>, - { - self.option_ids = val.into(); - self - } -} diff --git a/src/types/pre_checkout_query.rs b/src/types/pre_checkout_query.rs index 6b03f084..172e5e8f 100644 --- a/src/types/pre_checkout_query.rs +++ b/src/types/pre_checkout_query.rs @@ -37,71 +37,3 @@ pub struct PreCheckoutQuery { /// Order info provided by the user. pub order_info: Option, } - -impl PreCheckoutQuery { - pub fn new( - id: S1, - from: User, - currency: Currency, - total_amount: i32, - invoice_payload: S2, - ) -> Self - where - S1: Into, - S2: Into, - { - Self { - id: id.into(), - from, - currency, - total_amount, - invoice_payload: invoice_payload.into(), - shipping_option_id: None, - order_info: None, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn from(mut self, val: User) -> Self { - self.from = val; - self - } - - pub fn currency(mut self, val: Currency) -> Self { - self.currency = val; - self - } - - pub fn total_amount(mut self, val: i32) -> Self { - self.total_amount = val; - self - } - - pub fn invoice_payload(mut self, val: S) -> Self - where - S: Into, - { - self.invoice_payload = val.into(); - self - } - - pub fn shipping_option_id(mut self, val: S) -> Self - where - S: Into, - { - self.shipping_option_id = Some(val.into()); - self - } - - pub fn order_info(mut self, val: OrderInfo) -> Self { - self.order_info = Some(val); - self - } -} diff --git a/src/types/reply_keyboard_remove.rs b/src/types/reply_keyboard_remove.rs index 7a4d1643..2e9c81f6 100644 --- a/src/types/reply_keyboard_remove.rs +++ b/src/types/reply_keyboard_remove.rs @@ -37,15 +37,15 @@ pub struct ReplyKeyboardRemove { } impl ReplyKeyboardRemove { - pub fn new() -> Self { - Self::default() + pub const fn new() -> Self { + Self { + remove_keyboard: True, + selective: None, + } } - pub fn selective(mut self, val: T) -> Self - where - T: Into, - { - self.selective = Some(val.into()); + pub const fn selective(mut self, val: bool) -> Self { + self.selective = Some(val); self } } diff --git a/src/types/shipping_address.rs b/src/types/shipping_address.rs index 95ae4825..478ad714 100644 --- a/src/types/shipping_address.rs +++ b/src/types/shipping_address.rs @@ -24,76 +24,3 @@ pub struct ShippingAddress { /// Address post code. pub post_code: String, } - -impl ShippingAddress { - pub fn new( - country_code: CountryCode, - - state: S1, - city: S2, - street_line1: S3, - street_line2: S4, - post_code: S5, - ) -> Self - where - S1: Into, - S2: Into, - S3: Into, - S4: Into, - S5: Into, - { - Self { - country_code, - state: state.into(), - city: city.into(), - street_line1: street_line1.into(), - street_line2: street_line2.into(), - post_code: post_code.into(), - } - } - - pub fn country_code(mut self, val: CountryCode) -> Self { - self.country_code = val; - self - } - - pub fn state(mut self, val: S) -> Self - where - S: Into, - { - self.state = val.into(); - self - } - - pub fn city(mut self, val: S) -> Self - where - S: Into, - { - self.city = val.into(); - self - } - - pub fn street_line1(mut self, val: S) -> Self - where - S: Into, - { - self.street_line1 = val.into(); - self - } - - pub fn street_line2(mut self, val: S) -> Self - where - S: Into, - { - self.street_line2 = val.into(); - self - } - - pub fn post_code(mut self, val: S) -> Self - where - S: Into, - { - self.post_code = val.into(); - self - } -} diff --git a/src/types/shipping_query.rs b/src/types/shipping_query.rs index 97b0fd27..f8df55d4 100644 --- a/src/types/shipping_query.rs +++ b/src/types/shipping_query.rs @@ -19,49 +19,3 @@ pub struct ShippingQuery { /// User specified shipping address. pub shipping_address: ShippingAddress, } - -impl ShippingQuery { - pub fn new( - id: S1, - from: User, - invoice_payload: S2, - shipping_address: ShippingAddress, - ) -> Self - where - S1: Into, - S2: Into, - { - Self { - id: id.into(), - from, - invoice_payload: invoice_payload.into(), - shipping_address, - } - } - - pub fn id(mut self, val: S) -> Self - where - S: Into, - { - self.id = val.into(); - self - } - - pub fn from(mut self, val: User) -> Self { - self.from = val; - self - } - - pub fn invoice_payload(mut self, val: S) -> Self - where - S: Into, - { - self.invoice_payload = val.into(); - self - } - - pub fn shipping_address(mut self, val: ShippingAddress) -> Self { - self.shipping_address = val; - self - } -} diff --git a/src/types/sticker.rs b/src/types/sticker.rs index 8851e8ef..235f774d 100644 --- a/src/types/sticker.rs +++ b/src/types/sticker.rs @@ -42,93 +42,3 @@ pub struct Sticker { /// File size. pub file_size: Option, } - -impl Sticker { - pub fn new( - file_id: S1, - file_unique_id: S2, - width: u16, - height: u16, - is_animated: bool, - ) -> Self - where - S1: Into, - S2: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - width, - height, - is_animated, - thumb: None, - emoji: None, - set_name: None, - mask_position: None, - file_size: None, - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn height(mut self, val: u16) -> Self { - self.height = val; - self - } - - pub fn width(mut self, val: u16) -> Self { - self.width = val; - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_animated(mut self, val: bool) -> Self { - self.is_animated = val; - self - } - - pub fn thumb(mut self, val: PhotoSize) -> Self { - self.thumb = Some(val); - self - } - - pub fn emoji(mut self, val: S) -> Self - where - S: Into, - { - self.emoji = Some(val.into()); - self - } - - pub fn set_name(mut self, val: S) -> Self - where - S: Into, - { - self.set_name = Some(val.into()); - self - } - - pub fn mask_position(mut self, val: MaskPosition) -> Self { - self.mask_position = Some(val); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } -} diff --git a/src/types/sticker_set.rs b/src/types/sticker_set.rs index 0ffb4762..9fea1621 100644 --- a/src/types/sticker_set.rs +++ b/src/types/sticker_set.rs @@ -27,62 +27,3 @@ pub struct StickerSet { /// Sticker set thumbnail in the .WEBP or .TGS format. thumb: Option, } - -impl StickerSet { - pub fn new( - name: S1, - title: S2, - is_animated: bool, - contains_masks: bool, - stickers: St, - ) -> Self - where - S1: Into, - S2: Into, - St: Into>, - { - Self { - name: name.into(), - title: title.into(), - is_animated, - contains_masks, - stickers: stickers.into(), - thumb: None, - } - } - - pub fn name(mut self, val: S) -> Self - where - S: Into, - { - self.name = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_animated(mut self, val: bool) -> Self { - self.is_animated = val; - self - } - - pub fn contains_masks(mut self, val: bool) -> Self { - self.contains_masks = val; - self - } - - pub fn stickers(mut self, val: S) -> Self - where - S: Into>, - { - self.stickers = val.into(); - self - } -} diff --git a/src/types/successful_payment.rs b/src/types/successful_payment.rs index 4f7e43b5..d38a36b5 100644 --- a/src/types/successful_payment.rs +++ b/src/types/successful_payment.rs @@ -37,75 +37,3 @@ pub struct SuccessfulPayment { /// Provider payment identifier. pub provider_payment_charge_id: String, } - -impl SuccessfulPayment { - pub fn new( - currency: Currency, - total_amount: i32, - invoice_payload: S1, - telegram_payment_charge_id: S2, - provider_payment_charge_id: S3, - ) -> Self - where - S1: Into, - S2: Into, - S3: Into, - { - Self { - currency, - total_amount, - invoice_payload: invoice_payload.into(), - shipping_option_id: None, - order_info: None, - telegram_payment_charge_id: telegram_payment_charge_id.into(), - provider_payment_charge_id: provider_payment_charge_id.into(), - } - } - - pub fn currency(mut self, val: Currency) -> Self { - self.currency = val; - self - } - - pub fn total_amount(mut self, val: i32) -> Self { - self.total_amount = val; - self - } - - pub fn invoice_payload(mut self, val: S) -> Self - where - S: Into, - { - self.invoice_payload = val.into(); - self - } - - pub fn shipping_option_id(mut self, val: S) -> Self - where - S: Into, - { - self.shipping_option_id = Some(val.into()); - self - } - - pub fn order_info(mut self, val: OrderInfo) -> Self { - self.order_info = Some(val); - self - } - - pub fn telegram_payment_charge_id(mut self, val: S) -> Self - where - S: Into, - { - self.telegram_payment_charge_id = val.into(); - self - } - - pub fn provider_payment_charge_id(mut self, val: S) -> Self - where - S: Into, - { - self.provider_payment_charge_id = val.into(); - self - } -} diff --git a/src/types/update.rs b/src/types/update.rs index 51caf7ba..4ebc5710 100644 --- a/src/types/update.rs +++ b/src/types/update.rs @@ -28,22 +28,6 @@ pub struct Update { pub kind: UpdateKind, } -impl Update { - pub fn new(id: i32, kind: UpdateKind) -> Self { - Self { id, kind } - } - - pub fn id(mut self, val: i32) -> Self { - self.id = val; - self - } - - pub fn kind(mut self, val: UpdateKind) -> Self { - self.kind = val; - self - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum UpdateKind { diff --git a/src/types/user.rs b/src/types/user.rs index 7db4c14c..b575ef04 100644 --- a/src/types/user.rs +++ b/src/types/user.rs @@ -27,65 +27,6 @@ pub struct User { pub language_code: Option, } -impl User { - pub fn new(id: i32, is_bot: bool, first_name: S) -> Self - where - S: Into, - { - Self { - id, - is_bot, - first_name: first_name.into(), - last_name: None, - username: None, - language_code: None, - } - } - - pub fn id(mut self, val: i32) -> Self { - self.id = val; - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_bot(mut self, val: bool) -> Self { - self.is_bot = val; - self - } - - pub fn first_name(mut self, val: S) -> Self - where - S: Into, - { - self.first_name = val.into(); - self - } - - pub fn last_name(mut self, val: S) -> Self - where - S: Into, - { - self.last_name = Some(val.into()); - self - } - - pub fn username(mut self, val: S) -> Self - where - S: Into, - { - self.username = Some(val.into()); - self - } - - pub fn language_code(mut self, val: S) -> Self - where - S: Into, - { - self.language_code = Some(val.into()); - self - } -} - impl User { pub fn full_name(&self) -> String { match &self.last_name { diff --git a/src/types/user_profile_photos.rs b/src/types/user_profile_photos.rs index 05dec195..ac0a9b5d 100644 --- a/src/types/user_profile_photos.rs +++ b/src/types/user_profile_photos.rs @@ -13,30 +13,3 @@ pub struct UserProfilePhotos { /// Requested profile pictures (in up to 4 sizes each). pub photos: Vec>, } - -impl UserProfilePhotos { - pub fn new(total_count: u32, photos: P1) -> Self - where - P1: Into>, - P2: Into>, - { - Self { - total_count, - photos: photos.into().into_iter().map(Into::into).collect(), - } - } - - pub fn total_count(mut self, val: u32) -> Self { - self.total_count = val; - self - } - - pub fn photos(mut self, val: P1) -> Self - where - P1: Into>, - P2: Into>, - { - self.photos = val.into().into_iter().map(Into::into).collect(); - self - } -} diff --git a/src/types/venue.rs b/src/types/venue.rs index d8891a44..afb0cc90 100644 --- a/src/types/venue.rs +++ b/src/types/venue.rs @@ -23,51 +23,3 @@ pub struct Venue { /// `food/icecream`.) pub foursquare_type: Option, } - -impl Venue { - pub fn new(location: Location, title: S1, address: S2) -> Self - where - S1: Into, - S2: Into, - { - Self { - location, - title: title.into(), - address: address.into(), - foursquare_id: None, - foursquare_type: None, - } - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn address(mut self, val: S) -> Self - where - S: Into, - { - self.address = val.into(); - self - } - - pub fn foursquare_id(mut self, val: S) -> Self - where - S: Into, - { - self.foursquare_id = Some(val.into()); - self - } - - pub fn foursquare_type(mut self, val: S) -> Self - where - S: Into, - { - self.foursquare_type = Some(val.into()); - self - } -} diff --git a/src/types/video.rs b/src/types/video.rs index 1db5737f..bc8611ec 100644 --- a/src/types/video.rs +++ b/src/types/video.rs @@ -36,74 +36,3 @@ pub struct Video { /// File size. pub file_size: Option, } - -impl Video { - pub fn new( - file_id: S1, - file_unique_id: S2, - width: u32, - height: u32, - duration: u32, - ) -> Self - where - S1: Into, - S2: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - width, - height, - duration, - thumb: None, - mime_type: None, - file_size: None, - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn width(mut self, val: u32) -> Self { - self.width = val; - self - } - - pub fn height(mut self, val: u32) -> Self { - self.height = val; - self - } - - pub fn duration(mut self, val: u32) -> Self { - self.duration = val; - self - } - - pub fn thumb(mut self, val: PhotoSize) -> Self { - self.thumb = Some(val); - self - } - - pub fn mime_type(mut self, val: Mime) -> Self { - self.mime_type = Some(val); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } -} diff --git a/src/types/video_note.rs b/src/types/video_note.rs index d8c9ab3f..9a751e20 100644 --- a/src/types/video_note.rs +++ b/src/types/video_note.rs @@ -33,56 +33,3 @@ pub struct VideoNote { /// File size. pub file_size: Option, } - -impl VideoNote { - pub fn new(file_id: S1, file_unique_id: S2, length: u32, duration: u32) -> Self - where - S1: Into, - S2: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - length, - duration, - thumb: None, - file_size: None, - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn length(mut self, val: u32) -> Self { - self.length = val; - self - } - - pub fn duration(mut self, val: u32) -> Self { - self.duration = val; - self - } - - pub fn thumb(mut self, val: PhotoSize) -> Self { - self.thumb = Some(val); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } -} diff --git a/src/types/voice.rs b/src/types/voice.rs index 2fc7a5b9..7264b0c4 100644 --- a/src/types/voice.rs +++ b/src/types/voice.rs @@ -25,50 +25,3 @@ pub struct Voice { /// File size. pub file_size: Option, } - -impl Voice { - pub fn new(file_id: S1, file_unique_id: S2, duration: u32) -> Self - where - S1: Into, - S2: Into, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - duration, - mime_type: None, - file_size: None, - } - } - - pub fn file_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id(mut self, val: S) -> Self - where - S: Into, - { - self.file_unique_id = val.into(); - self - } - - pub fn duration(mut self, val: u32) -> Self { - self.duration = val; - self - } - - pub fn mime_type(mut self, val: Mime) -> Self { - self.mime_type = Some(val); - self - } - - pub fn file_size(mut self, val: u64) -> Self { - self.file_size = Some(val); - self - } -} diff --git a/src/types/webhook_info.rs b/src/types/webhook_info.rs index aad1cefb..e7658381 100644 --- a/src/types/webhook_info.rs +++ b/src/types/webhook_info.rs @@ -32,66 +32,3 @@ pub struct WebhookInfo { /// types. pub allowed_updates: Option>, } - -impl WebhookInfo { - pub fn new(url: S, has_custom_certificate: bool, pending_update_count: u32) -> Self - where - S: Into, - { - Self { - url: url.into(), - has_custom_certificate, - pending_update_count, - last_error_date: None, - - last_error_message: None, - max_connections: None, - allowed_updates: None, - } - } - - pub fn url(mut self, val: S) -> Self - where - S: Into, - { - self.url = val.into(); - self - } - - pub fn has_custom_certificate(mut self, val: bool) -> Self { - self.has_custom_certificate = val; - self - } - - pub fn pending_update_count(mut self, val: u32) -> Self { - self.pending_update_count = val; - self - } - - pub fn last_error_date(mut self, val: u64) -> Self { - self.last_error_date = Some(val); - self - } - - pub fn last_error_message(mut self, val: S) -> Self - where - S: Into, - { - self.last_error_message = Some(val.into()); - self - } - - pub fn max_connections(mut self, val: u32) -> Self { - self.max_connections = Some(val); - self - } - - pub fn allowed_updates(mut self, val: A) -> Self - where - A: Into>, - S: Into, - { - self.allowed_updates = Some(val.into().into_iter().map(Into::into).collect()); - self - } -} From 09d0d886fa612b96c10dce94dfe334ab80e5e981 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 26 Jan 2021 16:02:05 +0300 Subject: [PATCH 208/755] Refactor `KeyboardButtonPollType` - Make it enum - Rename `ButtonRequest::KeyboardButtonPollType` => `<_>::poll` --- src/types/keyboard_button.rs | 6 ++--- src/types/keyboard_button_poll_type.rs | 35 +++++++++++--------------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/types/keyboard_button.rs b/src/types/keyboard_button.rs index 2cefc957..aef83a28 100644 --- a/src/types/keyboard_button.rs +++ b/src/types/keyboard_button.rs @@ -49,7 +49,7 @@ impl KeyboardButton { pub enum ButtonRequest { Location, Contact, - KeyboardButtonPollType(KeyboardButtonPollType), + Poll(KeyboardButtonPollType), } /// Helper struct for (de)serializing [`ButtonRequest`](ButtonRequest) @@ -97,7 +97,7 @@ impl<'de> Deserialize<'de> for ButtonRequest { RawRequest { poll: Some(poll_type), .. - } => Ok(Self::KeyboardButtonPollType(poll_type)), + } => Ok(Self::Poll(poll_type)), _ => Err(D::Error::custom( "Either one of `request_contact` and `request_location` fields is required", )), @@ -123,7 +123,7 @@ impl Serialize for ButtonRequest { poll: None, } .serialize(serializer), - Self::KeyboardButtonPollType(poll_type) => RawRequest { + Self::Poll(poll_type) => RawRequest { contact: None, location: None, poll: Some(poll_type.clone()), diff --git a/src/types/keyboard_button_poll_type.rs b/src/types/keyboard_button_poll_type.rs index 07c7ddc6..6994fa72 100644 --- a/src/types/keyboard_button_poll_type.rs +++ b/src/types/keyboard_button_poll_type.rs @@ -1,25 +1,18 @@ use serde::{Deserialize, Serialize}; +/// This object represents type of a poll, which is allowed to be created and +/// sent when the corresponding button is pressed. #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub struct KeyboardButtonPollType { - poll_type: String, -} - -impl KeyboardButtonPollType { - pub fn new(poll_type: S) -> Self - where - S: Into, - { - Self { - poll_type: poll_type.into(), - } - } - - pub fn poll_type(mut self, val: S) -> Self - where - S: Into, - { - self.poll_type = val.into(); - self - } +#[serde(rename_all = "snake_case")] +#[serde(tag = "type")] +pub enum KeyboardButtonPollType { + /// If [`Quiz`] is passed, the user will be allowed to create only polls in + /// the quiz mode. + Quiz, + /// If [`Regular`] is passed, only regular polls will be allowed. + Regular, + /// If [`Any`] is passed, the user will be allowed to create a poll of any + /// type. + #[serde(rename = "")] + Any, } From bd4dba3bf11964886270d0e3fc578a9312c25b5b Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 26 Jan 2021 16:21:47 +0300 Subject: [PATCH 209/755] Remove `MessageEntity::text_from` because it's wrong (the method assumes UTF-8 indices, but the indices are UTF-16) (fuck you, telegram) --- src/types/message_entity.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs index fbd57216..872a1cb7 100644 --- a/src/types/message_entity.rs +++ b/src/types/message_entity.rs @@ -65,13 +65,6 @@ pub enum MessageEntityKind { Strikethrough, } -impl MessageEntity { - pub fn text_from(&self, message: &Message) -> Option { - let text = message.text(); - Some(String::from(&text?[self.offset..self.offset + self.length])) - } -} - #[cfg(test)] mod tests { use super::*; From 0ef05267438a5a092f582be24ee3c056f6b30915 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 26 Jan 2021 16:34:22 +0300 Subject: [PATCH 210/755] Replace `Into>` by `IntoIterator` --- src/types/inline_keyboard_markup.rs | 27 +++++++++++++++++++-------- src/types/message_entity.rs | 2 +- src/types/passport_element_error.rs | 16 ++++++++-------- src/types/reply_keyboard_markup.rs | 10 +++++++--- src/types/shipping_option.rs | 8 ++++---- 5 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/types/inline_keyboard_markup.rs b/src/types/inline_keyboard_markup.rs index 25618524..76294ddd 100644 --- a/src/types/inline_keyboard_markup.rs +++ b/src/types/inline_keyboard_markup.rs @@ -32,25 +32,36 @@ pub struct InlineKeyboardMarkup { impl InlineKeyboardMarkup { pub fn new(inline_keyboard: I1) -> Self where - I1: Into>, - I2: Into>, + I1: IntoIterator, + I2: IntoIterator, { Self { - inline_keyboard: inline_keyboard.into().into_iter().map(Into::into).collect(), + inline_keyboard: inline_keyboard + .into_iter() + .map(<_>::into_iter) + .map(<_>::collect) + .collect(), } } pub fn inline_keyboard(mut self, val: I1) -> Self where - I1: Into>, - I2: Into>, + I1: IntoIterator, + I2: IntoIterator, { - self.inline_keyboard = val.into().into_iter().map(Into::into).collect(); + self.inline_keyboard = val + .into_iter() + .map(<_>::into_iter) + .map(<_>::collect) + .collect(); self } - pub fn append_row(mut self, buttons: Vec) -> Self { - self.inline_keyboard.push(buttons); + pub fn append_row(mut self, buttons: R) -> Self + where + R: IntoIterator, + { + self.inline_keyboard.push(buttons.into_iter().collect()); self } diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs index 872a1cb7..e9742921 100644 --- a/src/types/message_entity.rs +++ b/src/types/message_entity.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::types::{Message, User}; +use crate::types::User; /// This object represents one special entity in a text message. /// diff --git a/src/types/passport_element_error.rs b/src/types/passport_element_error.rs index da96fba1..c94b6cf2 100644 --- a/src/types/passport_element_error.rs +++ b/src/types/passport_element_error.rs @@ -305,11 +305,11 @@ pub struct PassportElementErrorFiles { impl PassportElementErrorFiles { pub fn new(r#type: PassportElementErrorFilesType, file_hashes: S) -> Self where - S: Into>, + S: IntoIterator, { Self { r#type, - file_hashes: file_hashes.into(), + file_hashes: file_hashes.into_iter().collect(), } } @@ -320,9 +320,9 @@ impl PassportElementErrorFiles { pub fn file_hashes(mut self, val: S) -> Self where - S: Into>, + S: IntoIterator, { - self.file_hashes = val.into(); + self.file_hashes = val.into_iter().collect(); self } } @@ -386,11 +386,11 @@ pub struct PassportElementErrorTranslationFiles { impl PassportElementErrorTranslationFiles { pub fn new(r#type: PassportElementErrorTranslationFilesType, file_hashes: S) -> Self where - S: Into>, + S: IntoIterator, { Self { r#type, - file_hashes: file_hashes.into(), + file_hashes: file_hashes.into_iter().collect(), } } @@ -401,9 +401,9 @@ impl PassportElementErrorTranslationFiles { pub fn file_hashes(mut self, val: S) -> Self where - S: Into>, + S: IntoIterator, { - self.file_hashes = val.into(); + self.file_hashes = val.into_iter().collect(); self } } diff --git a/src/types/reply_keyboard_markup.rs b/src/types/reply_keyboard_markup.rs index 9289329e..b2e8d3b1 100644 --- a/src/types/reply_keyboard_markup.rs +++ b/src/types/reply_keyboard_markup.rs @@ -47,11 +47,15 @@ pub struct ReplyKeyboardMarkup { impl ReplyKeyboardMarkup { pub fn new(keyboard: K1) -> Self where - K1: Into>, - K2: Into>, + K1: IntoIterator, + K2: IntoIterator, { Self { - keyboard: keyboard.into().into_iter().map(Into::into).collect(), + keyboard: keyboard + .into_iter() + .map(<_>::into_iter) + .map(<_>::collect) + .collect(), resize_keyboard: None, one_time_keyboard: None, selective: None, diff --git a/src/types/shipping_option.rs b/src/types/shipping_option.rs index af154d6b..ec6e8a3e 100644 --- a/src/types/shipping_option.rs +++ b/src/types/shipping_option.rs @@ -22,12 +22,12 @@ impl ShippingOption { where S1: Into, S2: Into, - P: Into>, + P: IntoIterator, { Self { id: id.into(), title: title.into(), - prices: prices.into(), + prices: prices.into_iter().collect(), } } @@ -49,9 +49,9 @@ impl ShippingOption { pub fn prices

(mut self, val: P) -> Self where - P: Into>, + P: IntoIterator, { - self.prices = val.into(); + self.prices = val.into_iter().collect(); self } } From a26935d51a56f5f3fc77303d8cd367c8593b6090 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 26 Jan 2021 16:46:34 +0300 Subject: [PATCH 211/755] remove SendInvoice from types - It's a method not a telegram type - We already have SendInvoice ~at home~ in payloads Existence of the file was an accident --- src/types.rs | 1 - src/types/message_entity.rs | 11 +- src/types/send_invoice.rs | 228 ------------------------------------ 3 files changed, 1 insertion(+), 239 deletions(-) delete mode 100644 src/types/send_invoice.rs diff --git a/src/types.rs b/src/types.rs index d352caac..3b69e827 100644 --- a/src/types.rs +++ b/src/types.rs @@ -142,7 +142,6 @@ mod reply_keyboard_markup; mod reply_keyboard_remove; mod reply_markup; mod response_parameters; -mod send_invoice; mod shipping_address; mod shipping_option; mod shipping_query; diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs index e9742921..444f45ec 100644 --- a/src/types/message_entity.rs +++ b/src/types/message_entity.rs @@ -69,7 +69,7 @@ pub enum MessageEntityKind { mod tests { use super::*; use crate::types::{ - Chat, ChatKind, ChatPrivate, ForwardKind, ForwardOrigin, MediaKind, MediaText, + Chat, ChatKind, ChatPrivate, ForwardKind, ForwardOrigin, MediaKind, MediaText, Message, MessageCommon, MessageKind, }; @@ -111,15 +111,6 @@ mod tests { ); } - #[test] - fn text_from() { - let message = message(); - let expected = Some("yes".to_string()); - let entity = message.entities().unwrap()[0].clone(); - let actual = entity.text_from(&message); - assert_eq!(actual, expected); - } - fn message() -> Message { Message { via_bot: None, diff --git a/src/types/send_invoice.rs b/src/types/send_invoice.rs deleted file mode 100644 index c2aedb61..00000000 --- a/src/types/send_invoice.rs +++ /dev/null @@ -1,228 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{ChatId, InlineKeyboardMarkup, LabeledPrice}; - -// TODO: missing docs -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub struct SendInvoice { - pub chat_id: ChatId, - pub title: String, - pub description: String, - pub payload: String, - pub provider_token: String, - pub start_parameter: String, - pub currency: String, - pub prices: Vec, - pub provider_data: Option, - pub photo_url: Option, - pub photo_size: Option, - pub photo_width: Option, - pub photo_height: Option, - pub need_name: Option, - pub need_phone_number: Option, - pub need_email: Option, - pub need_shipping_address: Option, - pub send_phone_number_to_provider: Option, - pub send_email_to_provider: Option, - pub is_flexible: Option, - pub disable_notification: Option, - pub reply_to_message_id: Option, - pub reply_markup: Option, -} - -impl SendInvoice { - #[allow(clippy::too_many_arguments)] - pub fn new( - chat_id: C, - title: S1, - description: S2, - payload: S3, - provider_token: S4, - start_parameter: S5, - currency: S6, - prices: P, - ) -> Self - where - C: Into, - S1: Into, - S2: Into, - S3: Into, - S4: Into, - S5: Into, - S6: Into, - P: Into>, - { - Self { - chat_id: chat_id.into(), - title: title.into(), - description: description.into(), - payload: payload.into(), - provider_token: provider_token.into(), - start_parameter: start_parameter.into(), - currency: currency.into(), - prices: prices.into(), - provider_data: None, - photo_url: None, - photo_size: None, - photo_width: None, - photo_height: None, - need_name: None, - need_phone_number: None, - need_email: None, - need_shipping_address: None, - send_phone_number_to_provider: None, - send_email_to_provider: None, - is_flexible: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - pub fn chat_id(mut self, val: C) -> Self - where - C: Into, - { - self.chat_id = val.into(); - self - } - - pub fn title(mut self, val: S) -> Self - where - S: Into, - { - self.title = val.into(); - self - } - - pub fn description(mut self, val: S) -> Self - where - S: Into, - { - self.description = val.into(); - self - } - - pub fn payload(mut self, val: S) -> Self - where - S: Into, - { - self.payload = val.into(); - self - } - - pub fn provider_token(mut self, val: S) -> Self - where - S: Into, - { - self.provider_token = val.into(); - self - } - - pub fn start_parameter(mut self, val: S) -> Self - where - S: Into, - { - self.start_parameter = val.into(); - self - } - - pub fn currency(mut self, val: S) -> Self - where - S: Into, - { - self.currency = val.into(); - self - } - - pub fn prices

(mut self, val: P) -> Self - where - P: Into>, - { - self.prices = val.into(); - self - } - - pub fn provider_data(mut self, val: S) -> Self - where - S: Into, - { - self.provider_data = Some(val.into()); - self - } - - pub fn photo_url(mut self, val: S) -> Self - where - S: Into, - { - self.photo_url = Some(val.into()); - self - } - - pub fn photo_size(mut self, val: i32) -> Self { - self.photo_size = Some(val); - self - } - - pub fn photo_width(mut self, val: i32) -> Self { - self.photo_width = Some(val); - self - } - - pub fn photo_height(mut self, val: i32) -> Self { - self.photo_height = Some(val); - self - } - - pub fn need_name(mut self, val: bool) -> Self { - self.need_name = Some(val); - self - } - - pub fn need_phone_number(mut self, val: bool) -> Self { - self.need_phone_number = Some(val); - self - } - - pub fn need_email(mut self, val: bool) -> Self { - self.need_email = Some(val); - self - } - - pub fn need_shipping_address(mut self, val: bool) -> Self { - self.need_shipping_address = Some(val); - self - } - - pub fn send_phone_number_to_provider(mut self, val: bool) -> Self { - self.send_phone_number_to_provider = Some(val); - self - } - - pub fn send_email_to_provider(mut self, val: bool) -> Self { - self.send_email_to_provider = Some(val); - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_flexible(mut self, val: bool) -> Self { - self.is_flexible = Some(val); - self - } - - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - pub fn reply_to_message_id(mut self, value: i32) -> Self { - self.reply_to_message_id = Some(value); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} From cf4330d892d65c0a427c37798826a33f8d6ba83f Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 26 Jan 2021 16:52:33 +0300 Subject: [PATCH 212/755] fix 1584 line error --- src/types.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types.rs b/src/types.rs index 3b69e827..87e45a62 100644 --- a/src/types.rs +++ b/src/types.rs @@ -75,7 +75,6 @@ pub use reply_keyboard_markup::*; pub use reply_keyboard_remove::*; pub use reply_markup::*; pub use response_parameters::*; -pub use send_invoice::*; pub use shipping_address::*; pub use shipping_option::*; pub use shipping_query::*; From eaf72bd97d7c0139fcc529e2bf81aa4ae3b9fdfc Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 26 Jan 2021 16:53:49 +0300 Subject: [PATCH 213/755] remove unused function --- src/types/message_entity.rs | 41 ------------------------------------- 1 file changed, 41 deletions(-) diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs index 444f45ec..4bff8ea4 100644 --- a/src/types/message_entity.rs +++ b/src/types/message_entity.rs @@ -110,45 +110,4 @@ mod tests { .unwrap() ); } - - fn message() -> Message { - Message { - via_bot: None, - id: 0, - date: 0, - chat: Chat { - id: 0, - kind: ChatKind::Private(ChatPrivate { - type_: (), - username: None, - first_name: None, - last_name: None, - }), - photo: None, - }, - kind: MessageKind::Common(MessageCommon { - from: Some(User { - id: 0, - is_bot: false, - first_name: "".to_string(), - last_name: None, - username: None, - language_code: None, - }), - forward_kind: ForwardKind::Origin(ForwardOrigin { - reply_to_message: None, - }), - edit_date: None, - media_kind: MediaKind::Text(MediaText { - text: "no yes no".to_string(), - entities: vec![MessageEntity { - kind: MessageEntityKind::Mention, - offset: 3, - length: 3, - }], - }), - reply_markup: None, - }), - } - } } From 0e43c32b82496a17d5f5987f1f242ef40cddb0af Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 26 Jan 2021 18:31:15 +0300 Subject: [PATCH 214/755] clippy --- src/types/message_entity.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs index 4bff8ea4..a74fe6b1 100644 --- a/src/types/message_entity.rs +++ b/src/types/message_entity.rs @@ -68,10 +68,6 @@ pub enum MessageEntityKind { #[cfg(test)] mod tests { use super::*; - use crate::types::{ - Chat, ChatKind, ChatPrivate, ForwardKind, ForwardOrigin, MediaKind, MediaText, Message, - MessageCommon, MessageKind, - }; #[test] fn recursive_kind() { From 311a5b18fe4590126f04648e14028856df6f51b8 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 26 Jan 2021 18:39:10 +0300 Subject: [PATCH 215/755] fix docs --- src/types/keyboard_button_poll_type.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/types/keyboard_button_poll_type.rs b/src/types/keyboard_button_poll_type.rs index 6994fa72..1d9a7980 100644 --- a/src/types/keyboard_button_poll_type.rs +++ b/src/types/keyboard_button_poll_type.rs @@ -6,12 +6,12 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all = "snake_case")] #[serde(tag = "type")] pub enum KeyboardButtonPollType { - /// If [`Quiz`] is passed, the user will be allowed to create only polls in + /// If `Quiz` is passed, the user will be allowed to create only polls in /// the quiz mode. Quiz, - /// If [`Regular`] is passed, only regular polls will be allowed. + /// If `Regular` is passed, only regular polls will be allowed. Regular, - /// If [`Any`] is passed, the user will be allowed to create a poll of any + /// If `Any` is passed, the user will be allowed to create a poll of any /// type. #[serde(rename = "")] Any, From ec58a59903e3d94ef8fec05121eb6b4d15f703b7 Mon Sep 17 00:00:00 2001 From: Waffle Date: Fri, 5 Feb 2021 21:32:21 +0300 Subject: [PATCH 216/755] Fix SendMediaGroup return type Message -> Vec --- src/payloads/send_media_group.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/payloads/send_media_group.rs b/src/payloads/send_media_group.rs index 2241744a..2b7eb24f 100644 --- a/src/payloads/send_media_group.rs +++ b/src/payloads/send_media_group.rs @@ -10,7 +10,7 @@ impl_payload! { /// /// [`Message`]: crate::types::Message #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] - pub SendMediaGroup (SendMediaGroupSetters) => Message { + pub SendMediaGroup (SendMediaGroupSetters) => Vec { required { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) pub chat_id: ChatId [into], From 8befcffe269daaeefad35fa6f3712f0edf0ce9e4 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 6 Feb 2021 19:23:16 +0300 Subject: [PATCH 217/755] Fix naming that triggers clippy::upper_case_acronyms lint Rename - ParseMode::{HTML => Html} - ApiError::{ InvalidQueryID => InvalidQueryId, ButtonURLInvalid => ButtonUrlInvalid, WrongFileID => WrongFileId, WebhookRequireHTTPS => WebhookRequireHttps, WrongHTTPurl => WrongHttpUrl, } --- src/errors.rs | 10 +++++----- src/types/inline_query_result.rs | 2 +- src/types/parse_mode.rs | 7 ++++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 4efcae04..41c8e8e4 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -299,7 +299,7 @@ pub enum ApiError { rename = "Bad Request: query is too old and response timeout expired or query id is \ invalid" )] - InvalidQueryID, + InvalidQueryId, /// Occurs when bot tries to send InlineKeyboardMarkup with invalid button /// url. @@ -309,7 +309,7 @@ pub enum ApiError { /// /// [`SendMessage`]: crate::payloads::SendMessage #[serde(rename = "Bad Request: BUTTON_URL_INVALID")] - ButtonURLInvalid, + ButtonUrlInvalid, /// Occurs when bot tries to send button with data size more than 64 bytes. /// @@ -339,7 +339,7 @@ pub enum ApiError { /// /// [`GetFile`]: crate::payloads::GetFile #[serde(rename = "Bad Request: wrong file id")] - WrongFileID, + WrongFileId, /// Occurs when bot tries to do some with group which was deactivated. #[serde(rename = "Bad Request: group is deactivated")] @@ -413,7 +413,7 @@ pub enum ApiError { /// /// [`SetWebhook`]: crate::payloads::SetWebhook #[serde(rename = "Bad Request: bad webhook: HTTPS url must be provided for webhook")] - WebhookRequireHTTPS, + WebhookRequireHttps, /// Occurs when bot tries to set webhook to port other than 80, 88, 443 or /// 8443. @@ -509,7 +509,7 @@ pub enum ApiError { /// /// [`SendMessage`]: crate::payloads::SendMessage #[serde(rename = "Bad Request: wrong HTTP URL")] - WrongHTTPurl, + WrongHttpUrl, /// Occurs when bot tries GetUpdate before the timeout. Make sure that only /// one Updater is running. diff --git a/src/types/inline_query_result.rs b/src/types/inline_query_result.rs index e295bd15..e45f2e44 100644 --- a/src/types/inline_query_result.rs +++ b/src/types/inline_query_result.rs @@ -82,7 +82,7 @@ mod tests { id: String::from("id"), audio_file_id: String::from("audio_file_id"), caption: Some(String::from("caption")), - parse_mode: Some(ParseMode::HTML), + parse_mode: Some(ParseMode::Html), reply_markup: Some(InlineKeyboardMarkup::default()), input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), diff --git a/src/types/parse_mode.rs b/src/types/parse_mode.rs index 989b2f24..ac985af9 100644 --- a/src/types/parse_mode.rs +++ b/src/types/parse_mode.rs @@ -128,7 +128,8 @@ use serde::{Deserialize, Serialize}; #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum ParseMode { MarkdownV2, - HTML, + #[serde(rename = "HTML")] + Html, #[deprecated = "This is a legacy mode, retained for backward compatibility. Use `MarkdownV2` \ instead."] Markdown, @@ -140,7 +141,7 @@ impl TryFrom<&str> for ParseMode { fn try_from(value: &str) -> Result { let normalized = value.to_lowercase(); match normalized.as_ref() { - "html" => Ok(ParseMode::HTML), + "html" => Ok(ParseMode::Html), "markdown" => Ok(ParseMode::Markdown), "markdownv2" => Ok(ParseMode::MarkdownV2), _ => Err(()), @@ -173,7 +174,7 @@ mod tests { #[test] fn html_serialization() { let expected_json = String::from(r#""HTML""#); - let actual_json = serde_json::to_string(&ParseMode::HTML).unwrap(); + let actual_json = serde_json::to_string(&ParseMode::Html).unwrap(); assert_eq!(expected_json, actual_json) } From fc88c72a261a2eadaa37f78eef8ef183ab3600b4 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 6 Feb 2021 19:25:36 +0300 Subject: [PATCH 218/755] Suppress clippy::upper_case_acronyms for Currency and CountryCode --- src/types/non_telegram_types/country_code.rs | 1 + src/types/non_telegram_types/currency.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/types/non_telegram_types/country_code.rs b/src/types/non_telegram_types/country_code.rs index ddb4154e..459e7f35 100644 --- a/src/types/non_telegram_types/country_code.rs +++ b/src/types/non_telegram_types/country_code.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; /// ISO 3166-1 alpha-2 language code. +#[allow(clippy::upper_case_acronyms)] #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum CountryCode { /// Andorra diff --git a/src/types/non_telegram_types/currency.rs b/src/types/non_telegram_types/currency.rs index dcdec729..a252b48b 100644 --- a/src/types/non_telegram_types/currency.rs +++ b/src/types/non_telegram_types/currency.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; /// ISO 4217 currency. +#[allow(clippy::upper_case_acronyms)] #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum Currency { /// United Arab Emirates dirham From 21ecc39dc78aa56f7d011cbdce8311961ba0fa7a Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 7 Feb 2021 01:50:53 +0300 Subject: [PATCH 219/755] fix test --- src/requests/requester.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/requests/requester.rs b/src/requests/requester.rs index b8afc2e4..68ea02b9 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -29,7 +29,7 @@ use crate::{ /// // Required parameters are supplied to the `Requester` methods: /// bot.send_message(0, "Text") /// // Optional parameters can be supplied by calling setters -/// .parse_mode(ParseMode::HTML) +/// .parse_mode(ParseMode::Html) /// // To send request to telegram you need to call `.send()` and await the resulting future /// .send() /// .await?; From ab76ec07f277fccf3a9815b183b339c69fd32d2b Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 7 Feb 2021 02:41:29 +0300 Subject: [PATCH 220/755] fix docs --- src/types/inline_query_result_cached_gif.rs | 4 ++-- src/types/parse_mode.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/types/inline_query_result_cached_gif.rs b/src/types/inline_query_result_cached_gif.rs index 6712ccdf..6c0b8069 100644 --- a/src/types/inline_query_result_cached_gif.rs +++ b/src/types/inline_query_result_cached_gif.rs @@ -24,12 +24,12 @@ pub struct InlineQueryResultCachedGif { /// Caption of the GIF file to be sent, 0-1024 characters. pub caption: Option, - /// Send [`ParseMode::Markdown`] or [`ParseMode::HTML`], if you want + /// Send [`ParseMode::Markdown`] or [`ParseMode::Html`], if you want /// Telegram apps to show [bold, italic, fixed-width text or inline /// URLs] in the media caption. /// /// [`ParseMode::Markdown`]: crate::types::ParseMode::Markdown - /// [`ParseMode::HTML`]: crate::types::ParseMode::HTML + /// [`ParseMode::Html`]: crate::types::ParseMode::Html /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options pub parse_mode: Option, diff --git a/src/types/parse_mode.rs b/src/types/parse_mode.rs index ac985af9..7f0fba9a 100644 --- a/src/types/parse_mode.rs +++ b/src/types/parse_mode.rs @@ -69,7 +69,7 @@ use serde::{Deserialize, Serialize}; /// ignored. /// /// ## HTML style -/// To use this mode, pass [`HTML`] in the `parse_mode` field. +/// To use this mode, pass [`Html`] in the `parse_mode` field. /// The following tags are currently supported: /// ````text /// bold, bold @@ -123,7 +123,7 @@ use serde::{Deserialize, Serialize}; /// `*2*\**2=4*` for bold `2*2=4`. /// /// [`MarkdownV2`]: ParseMode::MarkdownV2 -/// [`HTML`]: ParseMode::HTML +/// [`Html`]: ParseMode::Html /// [`Markdown`]: ParseMode::Markdown #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum ParseMode { From d8c16d420dd395014a6452dff8467978570efa91 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 6 Feb 2021 19:07:10 +0600 Subject: [PATCH 221/755] refactor(throttle): small renamings --- src/adaptors/throttle.rs | 54 +++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index de56338a..63bbc842 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -86,12 +86,14 @@ const DELAY: Duration = Duration::from_millis(250); /// [@BotSupport]: https://t.me/botsupport #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct Limits { - /// Allowed messages in one chat per second - pub chat_s: u32, - /// Allowed messages per second - pub overall_s: u32, - /// Allowed messages in one chat per minute - pub chat_m: u32, + /// Allowed messages in one chat per second. + pub messages_per_sec_chat: u32, + + /// Allowed messages in one chat per minute. + pub messages_per_min_chat: u32, + + /// Allowed messages per second. + pub messages_per_sec_overall: u32, } /// Defaults are taken from [telegram documentation][tgdoc]. @@ -100,9 +102,9 @@ pub struct Limits { impl Default for Limits { fn default() -> Self { Self { - chat_s: 1, - overall_s: 30, - chat_m: 20, + messages_per_sec_chat: 1, + messages_per_sec_overall: 30, + messages_per_min_chat: 20, } } } @@ -152,29 +154,28 @@ pub struct Throttle { queue: mpsc::Sender<(Id, Sender)>, } -async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender)>) { +async fn worker(limits: Limits, mut rx: mpsc::Receiver<(Id, Sender)>) { // +- Same idea as in `Throttle::new` - let cap = limits.overall_s + (limits.overall_s / 4); + let capacity = limits.messages_per_sec_overall + (limits.messages_per_sec_overall / 4); // FIXME(waffle): Make an research about data structures for this queue. // Currently this is O(n) removing (n = number of elements // stayed), amortized O(1) push (vec+vecrem). - let mut queue: Vec<(Id, Sender)> = Vec::with_capacity(cap as usize); + let mut queue: Vec<(Id, Sender)> = Vec::with_capacity(capacity as usize); // I wish there was special data structure for history which removed the // need in 2 hashmaps // (waffle) let mut history: VecDeque<(Id, Instant)> = VecDeque::new(); - // hchats[chat] = history.iter().filter(|(c, _)| c == chat).count() let mut hchats: HashMap = HashMap::new(); let mut hchats_s = HashMap::new(); - // set to true when `queue_rx` is closed + // set to true when `rx` is closed let mut close = false; while !close || !queue.is_empty() { // If there are no pending requests we are just waiting if queue.is_empty() { - match queue_rx.recv().await { + match rx.recv().await { Some(req) => queue.push(req), None => close = true, } @@ -183,7 +184,7 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender) // update local queue with latest requests loop { // FIXME(waffle): https://github.com/tokio-rs/tokio/issues/3350 - match queue_rx.recv().now_or_never() { + match rx.recv().now_or_never() { Some(Some(req)) => queue.push(req), // There are no items in queue None => break, @@ -255,7 +256,7 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender) .iter() .take_while(|(_, time)| time > &sec_back) .count() as u32; - let mut allowed = limits.overall_s.saturating_sub(used); + let mut allowed = limits.messages_per_sec_overall.saturating_sub(used); if allowed == 0 { hchats_s.clear(); @@ -272,8 +273,8 @@ async fn worker(limits: Limits, mut queue_rx: mpsc::Receiver<(Id, Sender) while let Some(entry) = queue_rem.next() { let chat = &entry.value().0; let cond = { - hchats_s.get(chat).copied().unwrap_or(0) < limits.chat_s - && hchats.get(chat).copied().unwrap_or(0) < limits.chat_m + hchats_s.get(chat).copied().unwrap_or(0) < limits.messages_per_sec_chat + && hchats.get(chat).copied().unwrap_or(0) < limits.messages_per_min_chat }; if cond { @@ -312,14 +313,11 @@ impl Throttle { // so we won't lose performance when hitting limits. // // (I hope this makes sense) (waffle) - let buffer = limits.overall_s + (limits.overall_s / 8); - let (queue_tx, queue_rx) = mpsc::channel(buffer as usize); + let buffer_size = limits.messages_per_sec_overall + (limits.messages_per_sec_overall / 8); + let (tx, rx) = mpsc::channel(buffer_size as usize); - let worker = worker(limits, queue_rx); - let this = Self { - bot, - queue: queue_tx, - }; + let worker = worker(limits, rx); + let this = Self { bot, queue: tx }; (this, worker) } @@ -491,7 +489,7 @@ download_forward! { #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] enum Id { Id(i64), - Ch(u64), + ChannelUsernameHash(u64), } impl From<&ChatId> for Id { @@ -502,7 +500,7 @@ impl From<&ChatId> for Id { let mut hasher = std::collections::hash_map::DefaultHasher::new(); username.hash(&mut hasher); let hash = hasher.finish(); - Id::Ch(hash) + Id::ChannelUsernameHash(hash) } } } From 781371cb171a2990b6e966192b06e7c3504f572c Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 15 Feb 2021 00:28:52 +0300 Subject: [PATCH 222/755] update changelog --- CHANGELOG.md | 63 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc5baac2..4ff203ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,18 +9,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- `throttle`, `cache_me`, `auto_send` and `full` crate features +- Support for `rustls` ([#24][pr24]) +- `#[must_use]` attr to payloads implemented by macro ([#22][pr22]) +- forward-to-deref `Requester` impls ([#39][pr39]) +- `Bot::{set_,}api_url` methods ([#26][pr26], [#35][pr35]) - `payloads` module - `RequesterExt` trait which is implemented for all `Requester`s and allows easily wrapping them in adaptors -- `adaptors` module - - Request throttling - opt-in feature represented by `Throttle` bot adapter which allows automatically checking telegram limits ([#10][pr10]) +- `adaptors` module ([#14][pr14]) + - `throttle`, `cache_me`, `auto_send` and `full` crate features + - Request throttling - opt-in feature represented by `Throttle` bot adapter which allows automatically checking telegram limits ([#10][pr10], [#46][pr46]) - Request auto sending - ability to `.await` requests without need to call `.send()` (opt-in feature represented by `AutoSend` bot adapter, [#8][pr8]) - `get_me` caching (opt-in feature represented by `CacheMe` bot adapter) -- `Requester` trait which represents bot-clients ([#7][pr7]) +- `Requester` trait which represents bot-clients ([#7][pr7], [#12][pr12], [#27][pr27]) - `{Json,Multipart}Request` the `Bot` requests types ([#6][pr6]) - `Output` alias to `<::Payload as Payload>::Output` - `Payload`, `HasPayload` and `Request` traits which represent different parts of the request ([#5][pr5]) -- `GetUpdatesNonStrict` - fail proof version of `GetUpdates` +- `GetUpdatesNonStrict` 'telegram' method, that behaves just like `GetUpdates` but doesn't [#2][pr2] + fail if one of updates fails to be deserialized - Move core code here from the [`teloxide`] main repo, for older changes see it's [`CHANGELOG.md`]. - Following modules were moved: - `bot` @@ -31,18 +36,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `client_from_env` was moved from `teloxide::utils` to crate root of `teloxide-core` - To simplify `GetUpdates` request it was changed to simply return `Vec` (instead of `Vec>`) -- `GetUpdatesNonStrict` 'telegram' method, that behaves just like `GetUpdates` but doesn't - fail if one of updates fails to be deserialized +[pr2]: https://github.com/teloxide/teloxide-core/pull/2 [pr5]: https://github.com/teloxide/teloxide-core/pull/5 [pr6]: https://github.com/teloxide/teloxide-core/pull/6 [pr7]: https://github.com/teloxide/teloxide-core/pull/7 [pr8]: https://github.com/teloxide/teloxide-core/pull/8 [pr10]: https://github.com/teloxide/teloxide-core/pull/10 +[pr12]: https://github.com/teloxide/teloxide-core/pull/10 +[pr14]: https://github.com/teloxide/teloxide-core/pull/10 +[pr22]: https://github.com/teloxide/teloxide-core/pull/22 +[pr24]: https://github.com/teloxide/teloxide-core/pull/24 +[pr26]: https://github.com/teloxide/teloxide-core/pull/26 +[pr27]: https://github.com/teloxide/teloxide-core/pull/27 +[pr35]: https://github.com/teloxide/teloxide-core/pull/35 +[pr39]: https://github.com/teloxide/teloxide-core/pull/39 +[pr46]: https://github.com/teloxide/teloxide-core/pull/46 ### Changed -- Rename `StickerType` => `InputSticker`, `{CreateNewStickerSet,AddStickerToSet}::sticker_type}` => `sticker` ([#23][pr23]) +- Cleanup setters in `types::*` (remove most of them) ([#44][pr44]) +- Refactor `KeyboardButtonPollType` ([#44][pr44]) +- Replace `Into>` by `IntoIterator` in function arguments ([#44][pr44]) +- Update dependencies (including tokio 1.0) ([#37][pr37]) +- Refactor file downloading ([#30][pr30]): + - Make `net` module public + - Move `Bot::download_file{,_stream}` methods to a new `Download` trait + - Impl `Download` for all bot adaptors & the `Bot` itself + - Change return type of `download_file_stream` — return `Stream>``, + instead of `Future>>>`` + - Add `api_url` param to standalone versions of `download_file{,_stream}` + - Make `net::{TELEGRAM_API_URL, download_file{,_stream}}` pub +- Refactor `Bot` ([#29][pr29]): + - Move default parse mode to an adaptor (`DefaultParseMode`) + - Remove bot builder (it's not usefull anymore, since parse_mode is moved away) + - Undeprecate bot constructors (`Bot::{new, with_client, from_env_with_client}`) +- Rename `StickerType` => `InputSticker`, `{CreateNewStickerSet,AddStickerToSet}::sticker_type}` => `sticker` ([#23][pr23], [#43][pr43]) - Use `_: IntoIterator` bound instead of `_: Into>` in telegram methods which accept collections ([#21][pr21]) - Make `MessageDice::dice` pub ([#20][pr20]) - Merge `ApiErrorKind` and `KnownApiErrorKind` into `ApiError` ([#13][pr13]) @@ -50,24 +79,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Replace a bunch of `Option<_>` fields with `ChatMemberKind` - Remove setters (users are not expected to create this struct) - Add getters -- Changed internal mechanism of sending multipart requests +- Changed internal mechanism of sending multipart requests ([#1][pr1]) - Added `RequestError::Io(io::Error)` to wrap I/O error those can happen while sending files to telegram -- Change `StickerType`: instead of newtypes (`Png(InputFile)`) use structs (`Png { png_sticker: InputFile }`), add - `StickerType::{png,tgs}` constructors -- Make all fields of all methods `pub` +- Make all fields of all methods `pub` ([#3][pr3]) +[pr1]: https://github.com/teloxide/teloxide-core/pull/1 +[pr3]: https://github.com/teloxide/teloxide-core/pull/3 [pr9]: https://github.com/teloxide/teloxide-core/pull/9 [pr13]: https://github.com/teloxide/teloxide-core/pull/13 [pr20]: https://github.com/teloxide/teloxide-core/pull/20 [pr21]: https://github.com/teloxide/teloxide-core/pull/21 [pr23]: https://github.com/teloxide/teloxide-core/pull/23 +[pr29]: https://github.com/teloxide/teloxide-core/pull/29 +[pr30]: https://github.com/teloxide/teloxide-core/pull/30 +[pr37]: https://github.com/teloxide/teloxide-core/pull/37 +[pr43]: https://github.com/teloxide/teloxide-core/pull/43 ### Removed - `unstable-stream` feature (now `Bot::download_file_stream` is accesable by default) - old `Request` trait - `RequestWithFile`, now multipart requests use `Request` -- Remove all `#[non_exhaustive]` annotations +- Remove all `#[non_exhaustive]` annotations ([#4][pr4]) +- Remove `MessageEntity::text_from` because it's wrong ([#44][pr44]) + +[pr4]: https://github.com/teloxide/teloxide-core/pull/4 +[pr44]: https://github.com/teloxide/teloxide-core/pull/44 [`teloxide`]: https://github.com/teloxide/teloxide From 91220befb68524cd20f302ee9aa5d989ea609b78 Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 15 Feb 2021 00:35:53 +0300 Subject: [PATCH 223/755] fix warning --- src/types/passport_element_error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/passport_element_error.rs b/src/types/passport_element_error.rs index da96fba1..981d1232 100644 --- a/src/types/passport_element_error.rs +++ b/src/types/passport_element_error.rs @@ -38,8 +38,8 @@ impl PassportElementError { } } -#[serde(tag = "source")] #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[serde(tag = "source")] pub enum PassportElementErrorKind { #[serde(rename = "data")] DataField(PassportElementErrorDataField), From 3129d709fead5bff61de11ded40db2cea3cae6ee Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 15 Feb 2021 16:46:31 +0600 Subject: [PATCH 224/755] Small enhancements --- rustfmt.toml | 2 +- src/types/input_sticker.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index 60b85bac..9b0523d1 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,5 @@ format_code_in_doc_comments = true wrap_comments = true format_strings = true -merge_imports = true +imports_granularity = "Crate" use_field_init_shorthand = true diff --git a/src/types/input_sticker.rs b/src/types/input_sticker.rs index 8f765495..4e82f0ba 100644 --- a/src/types/input_sticker.rs +++ b/src/types/input_sticker.rs @@ -24,7 +24,7 @@ pub enum InputSticker { /// TGS animation with the sticker, uploaded using multipart/form-data. /// - /// See https://core.telegram.org/animated_stickers#technical-requirements for technical requirements + /// See for technical requirements #[serde(rename = "tgs_sticker")] Tgs(InputFile), } From d58b21c42f532ffe77c554c881ed8e029f663998 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 16 Feb 2021 00:50:46 +0600 Subject: [PATCH 225/755] Refactoring --- src/adaptors/throttle.rs | 239 ++++++++++++++++++++------------------- 1 file changed, 125 insertions(+), 114 deletions(-) diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index 63bbc842..6aca69d5 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -23,49 +23,48 @@ use crate::{ types::*, }; -// Throttling is quite complicated this comment describes the algorithm of -// current implementation. NOTE: this only describes CURRENT implementation. -// Implementation may change at any time. +// Throttling is quite complicated. This comment describes the algorithm of the +// current implementation. // // ### Request // -// When throttling request is sent, it sends a tuple of `ChatId` (more -// accurately, just local `Id`) and `Sender<()>` to the worker. Then the request -// waits for notification from worker. When notification is received it sends -// underlying request. +// When a throttling request is sent, it sends a tuple of `ChatId` and +// `Sender<()>` to the worker. Then the request waits for a notification from +// the worker. When notification is received, it sends the underlying request. // // ### Worker // -// Worker does the most important job - it checks for limit exceed. +// The worker does the most important job -- it ensures that the limits are +// never exceeded. // -// The worker stores "history" of requests sent in last minute (and to which -// chats the were sent) and queue of pending updates. +// The worker stores a history of requests sent in the last minute (and to which +// chats they were sent) and a queue of pending updates. // // The worker does the following algorithm loop: // -// 1. If queue is empty wait for the first message in incoming channel (and adds -// it to queue). +// 1. If the queue is empty, wait for the first message in incoming channel (and +// add it to the queue). // -// 2. Read all present messages from incoming channel and transfer them to -// queue. +// 2. Read all present messages from an incoming channel and transfer them to +// the queue. // -// 3. Record current time. +// 3. Record the current time. // -// 4. Clear history from records which time < (current - minute) +// 4. Clear the history from records whose time < (current time - minute). // -// 5. Count all requests in which were sent last second, -// `allowed = limit.overall_s - count` +// 5. Count all requests which were sent last second, `allowed = limit.overall_s +// - count`. // -// 6. If `allowed == 0` wait a bit and `continue` to the next iteration +// 6. If `allowed == 0` wait a bit and `continue` to the next iteration. // // 7. Count how many requests were sent to which chats (i.e.: create -// `Map`) (note: the same map, but for last minute also -// exists, but it's updated, instead of recreation) +// `Map`). (Note: the same map, but for last minute also exists, +// but it's updated, instead of recreation.) // -// 8. While `allowed >= 0` search for requests which chat hasn't exceed limits -// (i.e.: map[chat] < limit), if one is found, decrease `allowed`, notify -// request that it can be now executed, increase counts, add record to -// history. +// 8. While `allowed >= 0` search for requests which chat haven't exceed the +// limits (i.e.: map[chat] < limit), if one is found, decrease `allowed`, notify +// the request that it can be now executed, increase counts, add record to the +// history. const MINUTE: Duration = Duration::from_secs(60); const SECOND: Duration = Duration::from_secs(1); @@ -151,47 +150,34 @@ impl Default for Limits { pub struct Throttle { bot: B, // Sender is used to pass the signal to unlock by closing the channel. - queue: mpsc::Sender<(Id, Sender)>, + queue: mpsc::Sender<(FastChatId, Sender)>, } -async fn worker(limits: Limits, mut rx: mpsc::Receiver<(Id, Sender)>) { - // +- Same idea as in `Throttle::new` - let capacity = limits.messages_per_sec_overall + (limits.messages_per_sec_overall / 4); +type RequestsSent = u32; + +// I wish there was special data structure for history which removed the +// need in 2 hashmaps +// (waffle) +#[derive(Default)] +struct RequestsSentToChats { + per_min: HashMap, + per_sec: HashMap, +} + +async fn worker(limits: Limits, mut rx: mpsc::Receiver<(FastChatId, Sender)>) { // FIXME(waffle): Make an research about data structures for this queue. // Currently this is O(n) removing (n = number of elements // stayed), amortized O(1) push (vec+vecrem). - let mut queue: Vec<(Id, Sender)> = Vec::with_capacity(capacity as usize); + let mut queue: Vec<(FastChatId, Sender)> = + Vec::with_capacity(limits.messages_per_sec_overall as usize); - // I wish there was special data structure for history which removed the - // need in 2 hashmaps - // (waffle) - let mut history: VecDeque<(Id, Instant)> = VecDeque::new(); - let mut hchats: HashMap = HashMap::new(); - let mut hchats_s = HashMap::new(); + let mut when_requests_were_sent: VecDeque<(FastChatId, Instant)> = VecDeque::new(); + let mut requests_sent_to_chats = RequestsSentToChats::default(); - // set to true when `rx` is closed - let mut close = false; + let mut rx_is_closed = false; - while !close || !queue.is_empty() { - // If there are no pending requests we are just waiting - if queue.is_empty() { - match rx.recv().await { - Some(req) => queue.push(req), - None => close = true, - } - } - - // update local queue with latest requests - loop { - // FIXME(waffle): https://github.com/tokio-rs/tokio/issues/3350 - match rx.recv().now_or_never() { - Some(Some(req)) => queue.push(req), - // There are no items in queue - None => break, - // The queue was closed - Some(None) => close = true, - } - } + while !rx_is_closed || !queue.is_empty() { + read_from_rx(&mut rx, &mut queue, &mut rx_is_closed).await; // _Maybe_ we need to use `spawn_blocking` here, because there is // decent amount of blocking work. However _for now_ I've decided not @@ -231,18 +217,21 @@ async fn worker(limits: Limits, mut rx: mpsc::Receiver<(Id, Sender)>) { let sec_back = now - SECOND; // make history and hchats up-to-date - while let Some((_, time)) = history.front() { + while let Some((_, time)) = when_requests_were_sent.front() { // history is sorted, we found first up-to-date thing if time >= &min_back { break; } - if let Some((chat, _)) = history.pop_front() { - let ent = hchats.entry(chat).and_modify(|count| { - *count -= 1; - }); + if let Some((chat, _)) = when_requests_were_sent.pop_front() { + let entry = requests_sent_to_chats + .per_min + .entry(chat) + .and_modify(|count| { + *count -= 1; + }); - if let Entry::Occupied(entry) = ent { + if let Entry::Occupied(entry) = entry { if *entry.get() == 0 { entry.remove_entry(); } @@ -252,69 +241,90 @@ async fn worker(limits: Limits, mut rx: mpsc::Receiver<(Id, Sender)>) { // as truncates which is ok since in case of truncation it would always be >= // limits.overall_s - let used = history + let used = when_requests_were_sent .iter() .take_while(|(_, time)| time > &sec_back) .count() as u32; let mut allowed = limits.messages_per_sec_overall.saturating_sub(used); if allowed == 0 { - hchats_s.clear(); + requests_sent_to_chats.per_sec.clear(); tokio::time::sleep(DELAY).await; continue; } - for (chat, _) in history.iter().take_while(|(_, time)| time > &sec_back) { - *hchats_s.entry(*chat).or_insert(0) += 1; + for (chat, _) in when_requests_were_sent + .iter() + .take_while(|(_, time)| time > &sec_back) + { + *requests_sent_to_chats.per_sec.entry(*chat).or_insert(0) += 1; } - { - let mut queue_rem = queue.removing(); - while let Some(entry) = queue_rem.next() { - let chat = &entry.value().0; - let cond = { - hchats_s.get(chat).copied().unwrap_or(0) < limits.messages_per_sec_chat - && hchats.get(chat).copied().unwrap_or(0) < limits.messages_per_min_chat - }; + let mut queue_removing_iter = queue.removing(); - if cond { - { - *hchats_s.entry(*chat).or_insert(0) += 1; - *hchats.entry(*chat).or_insert(0) += 1; - history.push_back((*chat, Instant::now())); - } + while let Some(entry) = queue_removing_iter.next() { + let chat = &entry.value().0; + let messages_sent = requests_sent_to_chats + .per_sec + .get(chat) + .copied() + .unwrap_or(0); + let limits_not_exceeded = messages_sent < limits.messages_per_sec_chat + && messages_sent < limits.messages_per_min_chat; - // This will close the channel unlocking associated request - drop(entry.remove()); + if limits_not_exceeded { + *requests_sent_to_chats.per_sec.entry(*chat).or_insert(0) += 1; + *requests_sent_to_chats.per_min.entry(*chat).or_insert(0) += 1; + when_requests_were_sent.push_back((*chat, Instant::now())); - // We've "sent" 1 request, so now we can send 1 less - allowed -= 1; - if allowed == 0 { - break; - } + // Close the channel and unlock the associated request. + drop(entry.remove()); + + // We have "sent" one request, so now we can send one less. + allowed -= 1; + if allowed == 0 { + break; } } } // It's easier to just recompute last second stats, instead of keeping // track of it alongside with minute stats, so we just throw this away. - hchats_s.clear(); + requests_sent_to_chats.per_sec.clear(); tokio::time::sleep(DELAY).await; } } +async fn read_from_rx( + rx: &mut mpsc::Receiver<(FastChatId, Sender)>, + queue: &mut Vec<(FastChatId, Sender)>, + rx_is_closed: &mut bool, +) { + if queue.is_empty() { + match rx.recv().await { + Some(req) => queue.push(req), + None => *rx_is_closed = true, + } + } + + loop { + // FIXME(waffle): https://github.com/tokio-rs/tokio/issues/3350 + match rx.recv().now_or_never() { + Some(Some(req)) => queue.push(req), + Some(None) => *rx_is_closed = true, + // There are no items in queue. + None => break, + } + } +} + impl Throttle { /// Creates new [`Throttle`] alongside with worker future. /// /// Note: [`Throttle`] will only send requests if returned worker is /// polled/spawned/awaited. pub fn new(bot: B, limits: Limits) -> (Self, impl Future) { - // A buffer made slightly bigger (112.5%) than overall limit - // so we won't lose performance when hitting limits. - // - // (I hope this makes sense) (waffle) - let buffer_size = limits.messages_per_sec_overall + (limits.messages_per_sec_overall / 8); - let (tx, rx) = mpsc::channel(buffer_size as usize); + let (tx, rx) = mpsc::channel(limits.messages_per_sec_overall as usize); let worker = worker(limits, rx); let this = Self { bot, queue: tx }; @@ -448,7 +458,7 @@ where prices, ), self.queue.clone(), - |p| Id::Id(p.payload_ref().chat_id as _), + |p| FastChatId::Id(p.payload_ref().chat_id as _), ) } @@ -482,25 +492,25 @@ download_forward! { { this => this.inner() } } -/// Id used in worker. +/// An ID used in the worker. /// /// It is used instead of `ChatId` to make copying cheap even in case of -/// usernames. (It just hashes username) +/// usernames. (It is just a hashed username.) #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] -enum Id { +enum FastChatId { Id(i64), ChannelUsernameHash(u64), } -impl From<&ChatId> for Id { +impl From<&ChatId> for FastChatId { fn from(value: &ChatId) -> Self { match value { - ChatId::Id(id) => Id::Id(*id), + ChatId::Id(id) => FastChatId::Id(*id), ChatId::ChannelUsername(username) => { let mut hasher = std::collections::hash_map::DefaultHasher::new(); username.hash(&mut hasher); let hash = hasher.finish(); - Id::ChannelUsernameHash(hash) + FastChatId::ChannelUsernameHash(hash) } } } @@ -508,8 +518,8 @@ impl From<&ChatId> for Id { pub struct ThrottlingRequest( R, - mpsc::Sender<(Id, Sender)>, - fn(&R::Payload) -> Id, + mpsc::Sender<(FastChatId, Sender)>, + fn(&R::Payload) -> FastChatId, ); impl HasPayload for ThrottlingRequest { @@ -743,29 +753,30 @@ mod chan_send { use never::Never; use tokio::sync::{mpsc, mpsc::error::SendError, oneshot::Sender}; - use crate::adaptors::throttle::Id; + use crate::adaptors::throttle::FastChatId; pub(super) trait SendTy { - fn send_t(self, val: (Id, Sender)) -> ChanSend; + fn send_t(self, val: (FastChatId, Sender)) -> ChanSend; } #[pin_project::pin_project] pub(super) struct ChanSend(#[pin] Inner); #[cfg(not(feature = "nightly"))] - type Inner = Pin)>>> + Send>>; + type Inner = + Pin)>>> + Send>>; #[cfg(feature = "nightly")] - type Inner = impl Future)>>>; + type Inner = impl Future)>>>; - impl SendTy for mpsc::Sender<(Id, Sender)> { + impl SendTy for mpsc::Sender<(FastChatId, Sender)> { // `return`s trick IDEA not to show errors #[allow(clippy::needless_return)] - fn send_t(self, val: (Id, Sender)) -> ChanSend { + fn send_t(self, val: (FastChatId, Sender)) -> ChanSend { #[cfg(feature = "nightly")] { fn def( - sender: mpsc::Sender<(Id, Sender)>, - val: (Id, Sender), + sender: mpsc::Sender<(FastChatId, Sender)>, + val: (FastChatId, Sender), ) -> Inner { async move { sender.send(val).await } } @@ -780,7 +791,7 @@ mod chan_send { } impl Future for ChanSend { - type Output = Result<(), SendError<(Id, Sender)>>; + type Output = Result<(), SendError<(FastChatId, Sender)>>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.project().0.poll(cx) From 916a774c88bb33905abe6e73a15382b4cbd6adff Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 15 Feb 2021 22:44:19 +0300 Subject: [PATCH 226/755] Update src/adaptors/throttle.rs Co-authored-by: Waffle Lapkin --- src/adaptors/throttle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index 6aca69d5..789eda62 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -52,7 +52,7 @@ use crate::{ // // 4. Clear the history from records whose time < (current time - minute). // -// 5. Count all requests which were sent last second, `allowed = limit.overall_s +// 5. Count all requests which were sent last second, `allowed = limit.messages_per_sec_overall // - count`. // // 6. If `allowed == 0` wait a bit and `continue` to the next iteration. From 65c98741db4e98211470a1515bf74889fe2eb9e9 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 16 Feb 2021 01:57:59 +0600 Subject: [PATCH 227/755] Apply suggestions from the review --- src/adaptors/throttle.rs | 99 ++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 54 deletions(-) diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index 789eda62..3a4b0cd1 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -52,7 +52,8 @@ use crate::{ // // 4. Clear the history from records whose time < (current time - minute). // -// 5. Count all requests which were sent last second, `allowed = limit.messages_per_sec_overall +// 5. Count all requests which were sent last second, `allowed = +// limit.messages_per_sec_overall // - count`. // // 6. If `allowed == 0` wait a bit and `continue` to the next iteration. @@ -150,7 +151,7 @@ impl Default for Limits { pub struct Throttle { bot: B, // Sender is used to pass the signal to unlock by closing the channel. - queue: mpsc::Sender<(FastChatId, Sender)>, + queue: mpsc::Sender<(ChatIdHash, Sender)>, } type RequestsSent = u32; @@ -160,19 +161,19 @@ type RequestsSent = u32; // (waffle) #[derive(Default)] struct RequestsSentToChats { - per_min: HashMap, - per_sec: HashMap, + per_min: HashMap, + per_sec: HashMap, } -async fn worker(limits: Limits, mut rx: mpsc::Receiver<(FastChatId, Sender)>) { +async fn worker(limits: Limits, mut rx: mpsc::Receiver<(ChatIdHash, Sender)>) { // FIXME(waffle): Make an research about data structures for this queue. // Currently this is O(n) removing (n = number of elements // stayed), amortized O(1) push (vec+vecrem). - let mut queue: Vec<(FastChatId, Sender)> = + let mut queue: Vec<(ChatIdHash, Sender)> = Vec::with_capacity(limits.messages_per_sec_overall as usize); - let mut when_requests_were_sent: VecDeque<(FastChatId, Instant)> = VecDeque::new(); - let mut requests_sent_to_chats = RequestsSentToChats::default(); + let mut history: VecDeque<(ChatIdHash, Instant)> = VecDeque::new(); + let mut requests_sent = RequestsSentToChats::default(); let mut rx_is_closed = false; @@ -217,19 +218,16 @@ async fn worker(limits: Limits, mut rx: mpsc::Receiver<(FastChatId, Sender= &min_back { break; } - if let Some((chat, _)) = when_requests_were_sent.pop_front() { - let entry = requests_sent_to_chats - .per_min - .entry(chat) - .and_modify(|count| { - *count -= 1; - }); + if let Some((chat, _)) = history.pop_front() { + let entry = requests_sent.per_min.entry(chat).and_modify(|count| { + *count -= 1; + }); if let Entry::Occupied(entry) = entry { if *entry.get() == 0 { @@ -241,41 +239,34 @@ async fn worker(limits: Limits, mut rx: mpsc::Receiver<(FastChatId, Sender= // limits.overall_s - let used = when_requests_were_sent + let used = history .iter() .take_while(|(_, time)| time > &sec_back) .count() as u32; let mut allowed = limits.messages_per_sec_overall.saturating_sub(used); if allowed == 0 { - requests_sent_to_chats.per_sec.clear(); + requests_sent.per_sec.clear(); tokio::time::sleep(DELAY).await; continue; } - for (chat, _) in when_requests_were_sent - .iter() - .take_while(|(_, time)| time > &sec_back) - { - *requests_sent_to_chats.per_sec.entry(*chat).or_insert(0) += 1; + for (chat, _) in history.iter().take_while(|(_, time)| time > &sec_back) { + *requests_sent.per_sec.entry(*chat).or_insert(0) += 1; } - let mut queue_removing_iter = queue.removing(); + let mut queue_removing = queue.removing(); - while let Some(entry) = queue_removing_iter.next() { + while let Some(entry) = queue_removing.next() { let chat = &entry.value().0; - let messages_sent = requests_sent_to_chats - .per_sec - .get(chat) - .copied() - .unwrap_or(0); - let limits_not_exceeded = messages_sent < limits.messages_per_sec_chat - && messages_sent < limits.messages_per_min_chat; + let requests_sent = requests_sent.per_sec.get(chat).copied().unwrap_or(0); + let limits_not_exceeded = requests_sent < limits.messages_per_sec_chat + && requests_sent < limits.messages_per_min_chat; if limits_not_exceeded { - *requests_sent_to_chats.per_sec.entry(*chat).or_insert(0) += 1; - *requests_sent_to_chats.per_min.entry(*chat).or_insert(0) += 1; - when_requests_were_sent.push_back((*chat, Instant::now())); + *requests_sent.per_sec.entry(*chat).or_insert(0) += 1; + *requests_sent.per_min.entry(*chat).or_insert(0) += 1; + history.push_back((*chat, Instant::now())); // Close the channel and unlock the associated request. drop(entry.remove()); @@ -290,14 +281,14 @@ async fn worker(limits: Limits, mut rx: mpsc::Receiver<(FastChatId, Sender)>, - queue: &mut Vec<(FastChatId, Sender)>, + rx: &mut mpsc::Receiver<(ChatIdHash, Sender)>, + queue: &mut Vec<(ChatIdHash, Sender)>, rx_is_closed: &mut bool, ) { if queue.is_empty() { @@ -458,7 +449,7 @@ where prices, ), self.queue.clone(), - |p| FastChatId::Id(p.payload_ref().chat_id as _), + |p| ChatIdHash::Id(p.payload_ref().chat_id as _), ) } @@ -497,20 +488,20 @@ download_forward! { /// It is used instead of `ChatId` to make copying cheap even in case of /// usernames. (It is just a hashed username.) #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] -enum FastChatId { +enum ChatIdHash { Id(i64), ChannelUsernameHash(u64), } -impl From<&ChatId> for FastChatId { +impl From<&ChatId> for ChatIdHash { fn from(value: &ChatId) -> Self { match value { - ChatId::Id(id) => FastChatId::Id(*id), + ChatId::Id(id) => ChatIdHash::Id(*id), ChatId::ChannelUsername(username) => { let mut hasher = std::collections::hash_map::DefaultHasher::new(); username.hash(&mut hasher); let hash = hasher.finish(); - FastChatId::ChannelUsernameHash(hash) + ChatIdHash::ChannelUsernameHash(hash) } } } @@ -518,8 +509,8 @@ impl From<&ChatId> for FastChatId { pub struct ThrottlingRequest( R, - mpsc::Sender<(FastChatId, Sender)>, - fn(&R::Payload) -> FastChatId, + mpsc::Sender<(ChatIdHash, Sender)>, + fn(&R::Payload) -> ChatIdHash, ); impl HasPayload for ThrottlingRequest { @@ -753,10 +744,10 @@ mod chan_send { use never::Never; use tokio::sync::{mpsc, mpsc::error::SendError, oneshot::Sender}; - use crate::adaptors::throttle::FastChatId; + use crate::adaptors::throttle::ChatIdHash; pub(super) trait SendTy { - fn send_t(self, val: (FastChatId, Sender)) -> ChanSend; + fn send_t(self, val: (ChatIdHash, Sender)) -> ChanSend; } #[pin_project::pin_project] @@ -764,19 +755,19 @@ mod chan_send { #[cfg(not(feature = "nightly"))] type Inner = - Pin)>>> + Send>>; + Pin)>>> + Send>>; #[cfg(feature = "nightly")] - type Inner = impl Future)>>>; + type Inner = impl Future)>>>; - impl SendTy for mpsc::Sender<(FastChatId, Sender)> { + impl SendTy for mpsc::Sender<(ChatIdHash, Sender)> { // `return`s trick IDEA not to show errors #[allow(clippy::needless_return)] - fn send_t(self, val: (FastChatId, Sender)) -> ChanSend { + fn send_t(self, val: (ChatIdHash, Sender)) -> ChanSend { #[cfg(feature = "nightly")] { fn def( - sender: mpsc::Sender<(FastChatId, Sender)>, - val: (FastChatId, Sender), + sender: mpsc::Sender<(ChatIdHash, Sender)>, + val: (ChatIdHash, Sender), ) -> Inner { async move { sender.send(val).await } } @@ -791,7 +782,7 @@ mod chan_send { } impl Future for ChanSend { - type Output = Result<(), SendError<(FastChatId, Sender)>>; + type Output = Result<(), SendError<(ChatIdHash, Sender)>>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.project().0.poll(cx) From 8deaac04fad405d4905db74c7cffb243f6861541 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Tue, 16 Feb 2021 02:03:04 +0600 Subject: [PATCH 228/755] Fix Clippy --- src/adaptors/throttle.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index 3a4b0cd1..6d866a25 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -259,9 +259,9 @@ async fn worker(limits: Limits, mut rx: mpsc::Receiver<(ChatIdHash, Sender Date: Tue, 16 Feb 2021 02:09:06 +0600 Subject: [PATCH 229/755] Reformat throttle.rs --- src/adaptors/throttle.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index 6d866a25..86651891 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -53,8 +53,7 @@ use crate::{ // 4. Clear the history from records whose time < (current time - minute). // // 5. Count all requests which were sent last second, `allowed = -// limit.messages_per_sec_overall -// - count`. +// limit.messages_per_sec_overall - count`. // // 6. If `allowed == 0` wait a bit and `continue` to the next iteration. // From 8abf3863e5856ad850ac5053abd1ed6a76fde59a Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 16 Feb 2021 16:55:27 +0300 Subject: [PATCH 230/755] Future proof multipart requests --- CHANGELOG.md | 2 ++ src/bot.rs | 4 ++-- src/local_macros.rs | 12 +++++++++++- src/net/request.rs | 7 ++++--- src/payloads/add_sticker_to_set.rs | 3 ++- src/payloads/answer_callback_query.rs | 2 +- src/payloads/answer_inline_query.rs | 2 +- src/payloads/answer_pre_checkout_query.rs | 2 +- src/payloads/answer_shipping_query.rs | 2 +- src/payloads/create_new_sticker_set.rs | 3 ++- src/payloads/delete_chat_photo.rs | 2 +- src/payloads/delete_chat_sticker_set.rs | 2 +- src/payloads/delete_message.rs | 2 +- src/payloads/delete_sticker_from_set.rs | 2 +- src/payloads/delete_webhook.rs | 2 +- src/payloads/edit_message_caption.rs | 2 +- src/payloads/edit_message_caption_inline.rs | 2 +- src/payloads/edit_message_live_location.rs | 2 +- .../edit_message_live_location_inline.rs | 2 +- src/payloads/edit_message_media.rs | 2 +- src/payloads/edit_message_media_inline.rs | 2 +- src/payloads/edit_message_reply_markup.rs | 2 +- .../edit_message_reply_markup_inline.rs | 2 +- src/payloads/edit_message_text.rs | 2 +- src/payloads/edit_message_text_inline.rs | 2 +- src/payloads/export_chat_invite_link.rs | 2 +- src/payloads/forward_message.rs | 2 +- src/payloads/get_chat.rs | 2 +- src/payloads/get_chat_administrators.rs | 2 +- src/payloads/get_chat_member.rs | 2 +- src/payloads/get_chat_members_count.rs | 2 +- src/payloads/get_file.rs | 2 +- src/payloads/get_game_high_scores.rs | 2 +- src/payloads/get_me.rs | 2 +- src/payloads/get_my_commands.rs | 2 +- src/payloads/get_sticker_set.rs | 2 +- src/payloads/get_updates.rs | 2 +- src/payloads/get_user_profile_photos.rs | 2 +- src/payloads/get_webhook_info.rs | 2 +- src/payloads/kick_chat_member.rs | 2 +- src/payloads/leave_chat.rs | 2 +- src/payloads/pin_chat_message.rs | 2 +- src/payloads/promote_chat_member.rs | 2 +- src/payloads/restrict_chat_member.rs | 2 +- src/payloads/send_animation.rs | 3 ++- src/payloads/send_audio.rs | 3 ++- src/payloads/send_chat_action.rs | 2 +- src/payloads/send_contact.rs | 2 +- src/payloads/send_dice.rs | 2 +- src/payloads/send_document.rs | 3 ++- src/payloads/send_game.rs | 2 +- src/payloads/send_invoice.rs | 2 +- src/payloads/send_location.rs | 2 +- src/payloads/send_media_group.rs | 2 +- src/payloads/send_message.rs | 2 +- src/payloads/send_photo.rs | 3 ++- src/payloads/send_poll.rs | 2 +- src/payloads/send_sticker.rs | 3 ++- src/payloads/send_venue.rs | 2 +- src/payloads/send_video.rs | 3 ++- src/payloads/send_video_note.rs | 3 ++- src/payloads/send_voice.rs | 3 ++- .../set_chat_administrator_custom_title.rs | 2 +- src/payloads/set_chat_description.rs | 2 +- src/payloads/set_chat_permissions.rs | 2 +- src/payloads/set_chat_photo.rs | 3 ++- src/payloads/set_chat_sticker_set.rs | 2 +- src/payloads/set_chat_title.rs | 2 +- src/payloads/set_game_score.rs | 2 +- src/payloads/set_game_score_inline.rs | 2 +- src/payloads/set_my_commands.rs | 2 +- src/payloads/set_passport_data_errors.rs | 2 +- src/payloads/set_sticker_position_in_set.rs | 2 +- src/payloads/set_sticker_set_thumb.rs | 3 ++- src/payloads/set_webhook.rs | 3 ++- src/payloads/stop_message_live_location.rs | 2 +- .../stop_message_live_location_inline.rs | 2 +- src/payloads/stop_poll.rs | 2 +- src/payloads/unban_chat_member.rs | 2 +- src/payloads/unpin_chat_message.rs | 2 +- src/payloads/upload_sticker_file.rs | 3 ++- src/requests.rs | 6 ++++-- src/requests/multipart.rs | 12 ++++++------ src/requests/multipart_payload.rs | 19 +++++++++++++++++++ src/types/input_media.rs | 13 +++++++------ 85 files changed, 146 insertions(+), 97 deletions(-) create mode 100644 src/requests/multipart_payload.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ff203ba..07cda31b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `MultipartPayload` for future proofing ([#49][pr49]) - Support for `rustls` ([#24][pr24]) - `#[must_use]` attr to payloads implemented by macro ([#22][pr22]) - forward-to-deref `Requester` impls ([#39][pr39]) @@ -52,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [pr35]: https://github.com/teloxide/teloxide-core/pull/35 [pr39]: https://github.com/teloxide/teloxide-core/pull/39 [pr46]: https://github.com/teloxide/teloxide-core/pull/46 +[pr49]: https://github.com/teloxide/teloxide-core/pull/49 ### Changed diff --git a/src/bot.rs b/src/bot.rs index 06f6cea6..132bbaaa 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -9,7 +9,7 @@ use serde::{de::DeserializeOwned, Serialize}; use crate::{ bot::api_url::ApiUrl, net, - requests::{Payload, ResponseResult}, + requests::{MultipartPayload, Payload, ResponseResult}, serde_multipart, }; @@ -222,7 +222,7 @@ impl Bot { payload: &P, ) -> impl Future> where - P: Payload + Serialize, + P: MultipartPayload + Serialize, P::Output: DeserializeOwned, { let client = self.client.clone(); diff --git a/src/local_macros.rs b/src/local_macros.rs index a2549a00..1129e983 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -133,11 +133,14 @@ macro_rules! calculated_doc { ); } -/// Declare payload type, implement `Payload` trait amd ::new method for it, +/// Declare payload type, implement `Payload` trait and ::new method for it, /// declare setters trait and implement it for all type which have payload. #[macro_use] macro_rules! impl_payload { ( + $( + @[$multipart_attr:ident] + )? $( #[ $($method_meta:tt)* ] )* @@ -241,6 +244,8 @@ macro_rules! impl_payload { } impl

$Setters for P where P: crate::requests::HasPayload {} + + impl_payload! { @[$($multipart_attr)?] $Method req { $($($fields),*)? } opt { $($($opt_fields),*)? } } }; (@setter_opt $Method:ident $field:ident : $FTy:ty [into]) => { calculated_doc! { @@ -380,6 +385,11 @@ macro_rules! impl_payload { (@convert_map ($e:expr)) => { $e }; + (@[multipart] $Method:ident req { $($reqf:ident),* } opt { $($optf:ident),*} ) => { + impl crate::requests::MultipartPayload for $Method {} + impl crate::requests::multipart_payload::sealed::Sealed for $Method {} + }; + (@[] $($ignored:tt)*) => {} } #[macro_use] diff --git a/src/net/request.rs b/src/net/request.rs index b8726001..1ab76873 100644 --- a/src/net/request.rs +++ b/src/net/request.rs @@ -59,9 +59,10 @@ where tokio::time::sleep(DELAY_ON_SERVER_ERROR).await; } - serde_json::from_str::>( - &response.text().await.map_err(RequestError::NetworkError)?, - ) + serde_json::from_str::>(dbg!(&response + .text() + .await + .map_err(RequestError::NetworkError)?)) .map_err(RequestError::InvalidJson)? .into() } diff --git a/src/payloads/add_sticker_to_set.rs b/src/payloads/add_sticker_to_set.rs index e0eabc79..2541b79d 100644 --- a/src/payloads/add_sticker_to_set.rs +++ b/src/payloads/add_sticker_to_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -6,6 +6,7 @@ use serde::Serialize; use crate::types::{InputSticker, MaskPosition, True}; impl_payload! { + @[multipart] /// Use this method to add a new sticker to a set created by the bot. Animated stickers can be added to animated sticker sets and only to them. Animated sticker sets can have up to 50 stickers. Static sticker sets can have up to 120 stickers. Returns _True_ on success. #[derive(Debug, PartialEq, Clone, Serialize)] pub AddStickerToSet (AddStickerToSetSetters) => True { diff --git a/src/payloads/answer_callback_query.rs b/src/payloads/answer_callback_query.rs index 7cb28587..f5c3a849 100644 --- a/src/payloads/answer_callback_query.rs +++ b/src/payloads/answer_callback_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/answer_inline_query.rs b/src/payloads/answer_inline_query.rs index 8abddc59..ffc4f8ee 100644 --- a/src/payloads/answer_inline_query.rs +++ b/src/payloads/answer_inline_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/answer_pre_checkout_query.rs b/src/payloads/answer_pre_checkout_query.rs index a168b72b..356757c7 100644 --- a/src/payloads/answer_pre_checkout_query.rs +++ b/src/payloads/answer_pre_checkout_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/answer_shipping_query.rs b/src/payloads/answer_shipping_query.rs index 5572c136..c9d2b511 100644 --- a/src/payloads/answer_shipping_query.rs +++ b/src/payloads/answer_shipping_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/create_new_sticker_set.rs b/src/payloads/create_new_sticker_set.rs index ce2fc4bb..5c716a20 100644 --- a/src/payloads/create_new_sticker_set.rs +++ b/src/payloads/create_new_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -6,6 +6,7 @@ use serde::Serialize; use crate::types::{InputSticker, MaskPosition, True}; impl_payload! { + @[multipart] /// Use this method to create a new sticker set owned by a user. The bot will be able to edit the sticker set thus created. You must use exactly one of the fields _png\_sticker_ or _tgs\_sticker_. Returns _True_ on success. #[derive(Debug, PartialEq, Clone, Serialize)] pub CreateNewStickerSet (CreateNewStickerSetSetters) => True { diff --git a/src/payloads/delete_chat_photo.rs b/src/payloads/delete_chat_photo.rs index b27666aa..a6a5906e 100644 --- a/src/payloads/delete_chat_photo.rs +++ b/src/payloads/delete_chat_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_chat_sticker_set.rs b/src/payloads/delete_chat_sticker_set.rs index 1eaf60be..5070cff5 100644 --- a/src/payloads/delete_chat_sticker_set.rs +++ b/src/payloads/delete_chat_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_message.rs b/src/payloads/delete_message.rs index 22be46fc..3bf206f4 100644 --- a/src/payloads/delete_message.rs +++ b/src/payloads/delete_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_sticker_from_set.rs b/src/payloads/delete_sticker_from_set.rs index f4c4dc22..78d4f0c1 100644 --- a/src/payloads/delete_sticker_from_set.rs +++ b/src/payloads/delete_sticker_from_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_webhook.rs b/src/payloads/delete_webhook.rs index cf741a3d..6a2cf0d5 100644 --- a/src/payloads/delete_webhook.rs +++ b/src/payloads/delete_webhook.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_caption.rs b/src/payloads/edit_message_caption.rs index 58608b40..ddcdd6b9 100644 --- a/src/payloads/edit_message_caption.rs +++ b/src/payloads/edit_message_caption.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_caption_inline.rs b/src/payloads/edit_message_caption_inline.rs index 1d9d75cf..819d6b83 100644 --- a/src/payloads/edit_message_caption_inline.rs +++ b/src/payloads/edit_message_caption_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_live_location.rs b/src/payloads/edit_message_live_location.rs index 47d1f047..322e40e0 100644 --- a/src/payloads/edit_message_live_location.rs +++ b/src/payloads/edit_message_live_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_live_location_inline.rs b/src/payloads/edit_message_live_location_inline.rs index 99a477fd..1d650933 100644 --- a/src/payloads/edit_message_live_location_inline.rs +++ b/src/payloads/edit_message_live_location_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_media.rs b/src/payloads/edit_message_media.rs index ebf88244..958d147d 100644 --- a/src/payloads/edit_message_media.rs +++ b/src/payloads/edit_message_media.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_media_inline.rs b/src/payloads/edit_message_media_inline.rs index f1c2a823..8462873d 100644 --- a/src/payloads/edit_message_media_inline.rs +++ b/src/payloads/edit_message_media_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_reply_markup.rs b/src/payloads/edit_message_reply_markup.rs index 54f7344f..70d17489 100644 --- a/src/payloads/edit_message_reply_markup.rs +++ b/src/payloads/edit_message_reply_markup.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_reply_markup_inline.rs b/src/payloads/edit_message_reply_markup_inline.rs index ef3f0c9e..40af6ab8 100644 --- a/src/payloads/edit_message_reply_markup_inline.rs +++ b/src/payloads/edit_message_reply_markup_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_text.rs b/src/payloads/edit_message_text.rs index f49aa53a..5d8e72b5 100644 --- a/src/payloads/edit_message_text.rs +++ b/src/payloads/edit_message_text.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_text_inline.rs b/src/payloads/edit_message_text_inline.rs index 4ecdbfc3..90f08afc 100644 --- a/src/payloads/edit_message_text_inline.rs +++ b/src/payloads/edit_message_text_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/export_chat_invite_link.rs b/src/payloads/export_chat_invite_link.rs index 86b861c2..1e8fc54e 100644 --- a/src/payloads/export_chat_invite_link.rs +++ b/src/payloads/export_chat_invite_link.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/forward_message.rs b/src/payloads/forward_message.rs index 2afb7845..f1248b0f 100644 --- a/src/payloads/forward_message.rs +++ b/src/payloads/forward_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat.rs b/src/payloads/get_chat.rs index c04619d0..2abd7787 100644 --- a/src/payloads/get_chat.rs +++ b/src/payloads/get_chat.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat_administrators.rs b/src/payloads/get_chat_administrators.rs index 63af85dd..b950cbeb 100644 --- a/src/payloads/get_chat_administrators.rs +++ b/src/payloads/get_chat_administrators.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat_member.rs b/src/payloads/get_chat_member.rs index b29f05ed..e78ac0f4 100644 --- a/src/payloads/get_chat_member.rs +++ b/src/payloads/get_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat_members_count.rs b/src/payloads/get_chat_members_count.rs index dd48952b..4a363da0 100644 --- a/src/payloads/get_chat_members_count.rs +++ b/src/payloads/get_chat_members_count.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_file.rs b/src/payloads/get_file.rs index b5e29fd6..921ed5a7 100644 --- a/src/payloads/get_file.rs +++ b/src/payloads/get_file.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_game_high_scores.rs b/src/payloads/get_game_high_scores.rs index 565a607d..b61320a4 100644 --- a/src/payloads/get_game_high_scores.rs +++ b/src/payloads/get_game_high_scores.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_me.rs b/src/payloads/get_me.rs index a47285b7..d84ccbe5 100644 --- a/src/payloads/get_me.rs +++ b/src/payloads/get_me.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_my_commands.rs b/src/payloads/get_my_commands.rs index 8d75475a..29193161 100644 --- a/src/payloads/get_my_commands.rs +++ b/src/payloads/get_my_commands.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_sticker_set.rs b/src/payloads/get_sticker_set.rs index 96c96f7b..a30bfd2b 100644 --- a/src/payloads/get_sticker_set.rs +++ b/src/payloads/get_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_updates.rs b/src/payloads/get_updates.rs index 12965cc2..beaec909 100644 --- a/src/payloads/get_updates.rs +++ b/src/payloads/get_updates.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_user_profile_photos.rs b/src/payloads/get_user_profile_photos.rs index 02cc174e..2bc88808 100644 --- a/src/payloads/get_user_profile_photos.rs +++ b/src/payloads/get_user_profile_photos.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_webhook_info.rs b/src/payloads/get_webhook_info.rs index daf89088..bd3414e5 100644 --- a/src/payloads/get_webhook_info.rs +++ b/src/payloads/get_webhook_info.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/kick_chat_member.rs b/src/payloads/kick_chat_member.rs index a51d6c6c..d456192d 100644 --- a/src/payloads/kick_chat_member.rs +++ b/src/payloads/kick_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/leave_chat.rs b/src/payloads/leave_chat.rs index ad7b1e84..c96dc435 100644 --- a/src/payloads/leave_chat.rs +++ b/src/payloads/leave_chat.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/pin_chat_message.rs b/src/payloads/pin_chat_message.rs index 831d7fc6..feb81b30 100644 --- a/src/payloads/pin_chat_message.rs +++ b/src/payloads/pin_chat_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/promote_chat_member.rs b/src/payloads/promote_chat_member.rs index 9d4bc68c..b16b2a47 100644 --- a/src/payloads/promote_chat_member.rs +++ b/src/payloads/promote_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/restrict_chat_member.rs b/src/payloads/restrict_chat_member.rs index 10a1d685..10ba222c 100644 --- a/src/payloads/restrict_chat_member.rs +++ b/src/payloads/restrict_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_animation.rs b/src/payloads/send_animation.rs index 6e69e562..cd6d010e 100644 --- a/src/payloads/send_animation.rs +++ b/src/payloads/send_animation.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -6,6 +6,7 @@ use serde::Serialize; use crate::types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}; impl_payload! { + @[multipart] /// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound). On success, the sent [`Message`] is returned. Bots can currently send animation files of up to 50 MB in size, this limit may be changed in the future. /// /// [`Message`]: crate::types::Message diff --git a/src/payloads/send_audio.rs b/src/payloads/send_audio.rs index 9ae5a396..95bc7f21 100644 --- a/src/payloads/send_audio.rs +++ b/src/payloads/send_audio.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -6,6 +6,7 @@ use serde::Serialize; use crate::types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}; impl_payload! { + @[multipart] /// Use this method to send audio files, if you want Telegram clients to display them in the music player. Your audio must be in the .MP3 or .M4A format. On success, the sent [`Message`] is returned. Bots can currently send audio files of up to 50 MB in size, this limit may be changed in the future. /// /// For sending voice messages, use the [`SendVoice`] method instead. diff --git a/src/payloads/send_chat_action.rs b/src/payloads/send_chat_action.rs index 90cf1661..8b614607 100644 --- a/src/payloads/send_chat_action.rs +++ b/src/payloads/send_chat_action.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_contact.rs b/src/payloads/send_contact.rs index e97d6148..215cd02f 100644 --- a/src/payloads/send_contact.rs +++ b/src/payloads/send_contact.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_dice.rs b/src/payloads/send_dice.rs index 759a6146..7f885f91 100644 --- a/src/payloads/send_dice.rs +++ b/src/payloads/send_dice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_document.rs b/src/payloads/send_document.rs index de5854df..828b7314 100644 --- a/src/payloads/send_document.rs +++ b/src/payloads/send_document.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -6,6 +6,7 @@ use serde::Serialize; use crate::types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}; impl_payload! { + @[multipart] /// Use this method to send general files. On success, the sent [`Message`] is returned. Bots can currently send files of any type of up to 50 MB in size, this limit may be changed in the future. /// /// [`Message`]: crate::types::Message diff --git a/src/payloads/send_game.rs b/src/payloads/send_game.rs index b16536d9..e62e557f 100644 --- a/src/payloads/send_game.rs +++ b/src/payloads/send_game.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_invoice.rs b/src/payloads/send_invoice.rs index e6b6dd06..db8740af 100644 --- a/src/payloads/send_invoice.rs +++ b/src/payloads/send_invoice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_location.rs b/src/payloads/send_location.rs index 712072bb..f95cc19e 100644 --- a/src/payloads/send_location.rs +++ b/src/payloads/send_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_media_group.rs b/src/payloads/send_media_group.rs index 2b7eb24f..9593a330 100644 --- a/src/payloads/send_media_group.rs +++ b/src/payloads/send_media_group.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_message.rs b/src/payloads/send_message.rs index e8757285..609bfb28 100644 --- a/src/payloads/send_message.rs +++ b/src/payloads/send_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_photo.rs b/src/payloads/send_photo.rs index b1ea5ae4..6d9b8fe1 100644 --- a/src/payloads/send_photo.rs +++ b/src/payloads/send_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -6,6 +6,7 @@ use serde::Serialize; use crate::types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}; impl_payload! { + @[multipart] /// Use this method to send photos. On success, the sent [`Message`] is returned. /// /// [`Message`]: crate::types::Message diff --git a/src/payloads/send_poll.rs b/src/payloads/send_poll.rs index 870c8c0b..cbe48b33 100644 --- a/src/payloads/send_poll.rs +++ b/src/payloads/send_poll.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_sticker.rs b/src/payloads/send_sticker.rs index f2d7254e..3992ab53 100644 --- a/src/payloads/send_sticker.rs +++ b/src/payloads/send_sticker.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -6,6 +6,7 @@ use serde::Serialize; use crate::types::{ChatId, InputFile, Message, ReplyMarkup}; impl_payload! { + @[multipart] /// Use this method to send static .WEBP or [animated] .TGS stickers. On success, the sent Message is returned. /// /// [animated]: https://telegram.org/blog/animated-stickers diff --git a/src/payloads/send_venue.rs b/src/payloads/send_venue.rs index 7d47446c..361db666 100644 --- a/src/payloads/send_venue.rs +++ b/src/payloads/send_venue.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_video.rs b/src/payloads/send_video.rs index db789a09..49b585d5 100644 --- a/src/payloads/send_video.rs +++ b/src/payloads/send_video.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -6,6 +6,7 @@ use serde::Serialize; use crate::types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}; impl_payload! { + @[multipart] /// Use this method to send video files, Telegram clients support mp4 videos (other formats may be sent as [`Document`]). On success, the sent [`Message`] is returned. Bots can currently send video files of up to 50 MB in size, this limit may be changed in the future. /// /// [`Document`]: crate::types::Document diff --git a/src/payloads/send_video_note.rs b/src/payloads/send_video_note.rs index c53c00bc..49f6ac29 100644 --- a/src/payloads/send_video_note.rs +++ b/src/payloads/send_video_note.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -6,6 +6,7 @@ use serde::Serialize; use crate::types::{ChatId, InputFile, Message, ReplyMarkup}; impl_payload! { + @[multipart] /// 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. On success, the sent [`Message`] is returned. /// /// [v.4.0]: https://core.telegram.org/bots/api#document diff --git a/src/payloads/send_voice.rs b/src/payloads/send_voice.rs index 77605534..9b7a7698 100644 --- a/src/payloads/send_voice.rs +++ b/src/payloads/send_voice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -6,6 +6,7 @@ use serde::Serialize; use crate::types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}; impl_payload! { + @[multipart] /// Use this method to send audio files, if you want Telegram clients to display the file as a playable voice message. For this to work, your audio must be in an .OGG file encoded with OPUS (other formats may be sent as [`Audio`] or [`Document`]). On success, the sent [`Message`] is returned. Bots can currently send voice messages of up to 50 MB in size, this limit may be changed in the future. /// /// [`Document`]: crate::types::Document diff --git a/src/payloads/set_chat_administrator_custom_title.rs b/src/payloads/set_chat_administrator_custom_title.rs index 8eb39719..bf9ea9f4 100644 --- a/src/payloads/set_chat_administrator_custom_title.rs +++ b/src/payloads/set_chat_administrator_custom_title.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_description.rs b/src/payloads/set_chat_description.rs index 9e0650f7..f5239b2e 100644 --- a/src/payloads/set_chat_description.rs +++ b/src/payloads/set_chat_description.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_permissions.rs b/src/payloads/set_chat_permissions.rs index 8aa73f8c..c41b0a77 100644 --- a/src/payloads/set_chat_permissions.rs +++ b/src/payloads/set_chat_permissions.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_photo.rs b/src/payloads/set_chat_photo.rs index 7578ae27..4d4d4d3b 100644 --- a/src/payloads/set_chat_photo.rs +++ b/src/payloads/set_chat_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -6,6 +6,7 @@ use serde::Serialize; use crate::types::{ChatId, InputFile}; impl_payload! { + @[multipart] /// Use this method to set a new profile photo for the chat. Photos can't be changed for private chats. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. Returns _True_ on success. #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] pub SetChatPhoto (SetChatPhotoSetters) => String { diff --git a/src/payloads/set_chat_sticker_set.rs b/src/payloads/set_chat_sticker_set.rs index 353f12f3..4ce833b9 100644 --- a/src/payloads/set_chat_sticker_set.rs +++ b/src/payloads/set_chat_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_title.rs b/src/payloads/set_chat_title.rs index 06c77581..bdd605b6 100644 --- a/src/payloads/set_chat_title.rs +++ b/src/payloads/set_chat_title.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_game_score.rs b/src/payloads/set_game_score.rs index 75a32821..a0cda56f 100644 --- a/src/payloads/set_game_score.rs +++ b/src/payloads/set_game_score.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_game_score_inline.rs b/src/payloads/set_game_score_inline.rs index 4adbc8ac..c1f699c3 100644 --- a/src/payloads/set_game_score_inline.rs +++ b/src/payloads/set_game_score_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_my_commands.rs b/src/payloads/set_my_commands.rs index 854fa351..2919a13b 100644 --- a/src/payloads/set_my_commands.rs +++ b/src/payloads/set_my_commands.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_passport_data_errors.rs b/src/payloads/set_passport_data_errors.rs index 021ce4b9..05321a36 100644 --- a/src/payloads/set_passport_data_errors.rs +++ b/src/payloads/set_passport_data_errors.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_sticker_position_in_set.rs b/src/payloads/set_sticker_position_in_set.rs index 30abb8a1..45804e7e 100644 --- a/src/payloads/set_sticker_position_in_set.rs +++ b/src/payloads/set_sticker_position_in_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_sticker_set_thumb.rs b/src/payloads/set_sticker_set_thumb.rs index 62e7af35..a515e230 100644 --- a/src/payloads/set_sticker_set_thumb.rs +++ b/src/payloads/set_sticker_set_thumb.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -6,6 +6,7 @@ use serde::Serialize; use crate::types::{InputFile, True}; impl_payload! { + @[multipart] /// Use this method to set the thumbnail of a sticker set. Animated thumbnails can be set for animated sticker sets only. Returns _True_ on success. #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] pub SetStickerSetThumb (SetStickerSetThumbSetters) => True { diff --git a/src/payloads/set_webhook.rs b/src/payloads/set_webhook.rs index 65ee07e1..f584b6de 100644 --- a/src/payloads/set_webhook.rs +++ b/src/payloads/set_webhook.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -6,6 +6,7 @@ use serde::Serialize; use crate::types::{AllowedUpdate, InputFile, True}; impl_payload! { + @[multipart] /// Use this method to specify a url and receive incoming updates via an outgoing webhook. Whenever there is an update for the bot, we will send an HTTPS POST request to the specified url, containing a JSON-serialized [`Update`]. In case of an unsuccessful request, we will give up after a reasonable amount of attempts. Returns True on success. /// /// If you'd like to make sure that the Webhook request comes from Telegram, we recommend using a secret path in the URL, e.g. `https://www.example.com/`. Since nobody else knows your bot's token, you can be pretty sure it's us. diff --git a/src/payloads/stop_message_live_location.rs b/src/payloads/stop_message_live_location.rs index e93caada..ae251505 100644 --- a/src/payloads/stop_message_live_location.rs +++ b/src/payloads/stop_message_live_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/stop_message_live_location_inline.rs b/src/payloads/stop_message_live_location_inline.rs index b51a3572..09519515 100644 --- a/src/payloads/stop_message_live_location_inline.rs +++ b/src/payloads/stop_message_live_location_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/stop_poll.rs b/src/payloads/stop_poll.rs index 1d50c4bf..708e920f 100644 --- a/src/payloads/stop_poll.rs +++ b/src/payloads/stop_poll.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/unban_chat_member.rs b/src/payloads/unban_chat_member.rs index 05c46665..4b7ae1ba 100644 --- a/src/payloads/unban_chat_member.rs +++ b/src/payloads/unban_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/unpin_chat_message.rs b/src/payloads/unpin_chat_message.rs index d01945e2..04471d8c 100644 --- a/src/payloads/unpin_chat_message.rs +++ b/src/payloads/unpin_chat_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/upload_sticker_file.rs b/src/payloads/upload_sticker_file.rs index c303f05b..e8777d76 100644 --- a/src/payloads/upload_sticker_file.rs +++ b/src/payloads/upload_sticker_file.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (be02d84). +// This file is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -6,6 +6,7 @@ use serde::Serialize; use crate::types::{File, InputFile}; impl_payload! { + @[multipart] /// Use this method to upload a .PNG file with a sticker for later use in _createNewStickerSet_ and _addStickerToSet_ methods (can be used multiple times). Returns the uploaded File on success. #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] pub UploadStickerFile (UploadStickerFileSetters) => File { diff --git a/src/requests.rs b/src/requests.rs index 84e933e6..4ed846f3 100644 --- a/src/requests.rs +++ b/src/requests.rs @@ -1,8 +1,9 @@ //! Telegram API requests. pub use self::{ - has_payload::HasPayload, json::JsonRequest, multipart::MultipartRequest, payload::Payload, - request::Request, requester::Requester, requester_ext::RequesterExt, + has_payload::HasPayload, json::JsonRequest, multipart::MultipartRequest, + multipart_payload::MultipartPayload, payload::Payload, request::Request, requester::Requester, + requester_ext::RequesterExt, }; /// A type that is returned after making a request to Telegram. @@ -14,6 +15,7 @@ pub type Output = <::Payload as Payload>::Output; mod has_payload; mod json; mod multipart; +pub(crate) mod multipart_payload; mod payload; mod request; mod requester; diff --git a/src/requests/multipart.rs b/src/requests/multipart.rs index 5b2ea867..87d5f884 100644 --- a/src/requests/multipart.rs +++ b/src/requests/multipart.rs @@ -2,7 +2,7 @@ use serde::{de::DeserializeOwned, Serialize}; use crate::{ bot::Bot, - requests::{HasPayload, Payload, Request, ResponseResult}, + requests::{HasPayload, MultipartPayload, Payload, Request, ResponseResult}, RequestError, }; @@ -33,7 +33,7 @@ where // (though critically, currently we have no // non-'static payloads) P: 'static, - P: Payload + Serialize, + P: Payload + MultipartPayload + Serialize, P::Output: DeserializeOwned, { type Err = RequestError; @@ -67,7 +67,7 @@ where impl

core::ops::Deref for MultipartRequest

where P: 'static, - P: Payload + Serialize, + P: Payload + MultipartPayload, P::Output: DeserializeOwned, { type Target = P; @@ -80,7 +80,7 @@ where impl

core::ops::DerefMut for MultipartRequest

where P: 'static, - P: Payload + Serialize, + P: Payload + MultipartPayload, P::Output: DeserializeOwned, { fn deref_mut(&mut self) -> &mut Self::Target { @@ -95,7 +95,7 @@ req_future! { pub Send (inner0) -> ResponseResult where U: 'static, - U: Payload + Serialize, + U: Payload + MultipartPayload + Serialize, U::Output: DeserializeOwned, } @@ -106,6 +106,6 @@ req_future! { pub SendRef (inner1) -> ResponseResult where U: 'static, - U: Payload + Serialize, + U: Payload + MultipartPayload + Serialize, U::Output: DeserializeOwned, } diff --git a/src/requests/multipart_payload.rs b/src/requests/multipart_payload.rs new file mode 100644 index 00000000..49c5d63d --- /dev/null +++ b/src/requests/multipart_payload.rs @@ -0,0 +1,19 @@ +use crate::{payloads, requests::Payload}; + +/// This is a future proof trait. It is `sealed` and can change at any time. +pub trait MultipartPayload: Payload + sealed::Sealed {} + +// HACK(waffle): Sealed trait allows us to change `MultipartPayload` without +// breaking changes & refactor multipart requests later. +pub(crate) mod sealed { + pub trait Sealed {} +} + +impl sealed::Sealed for payloads::SendMediaGroup {} +impl MultipartPayload for payloads::SendMediaGroup {} + +impl sealed::Sealed for payloads::EditMessageMedia {} +impl MultipartPayload for payloads::EditMessageMedia {} + +impl sealed::Sealed for payloads::EditMessageMediaInline {} +impl MultipartPayload for payloads::EditMessageMediaInline {} diff --git a/src/types/input_media.rs b/src/types/input_media.rs index 2cdb4637..6726c0f5 100644 --- a/src/types/input_media.rs +++ b/src/types/input_media.rs @@ -410,9 +410,9 @@ impl InputMediaDocument { } } -impl InputMedia { - pub fn media(&self) -> &InputFile { - match self { +impl From for InputFile { + fn from(media: InputMedia) -> InputFile { + match media { InputMedia::Photo(InputMediaPhoto { media, .. }) | InputMedia::Document(InputMediaDocument { media, .. }) | InputMedia::Audio(InputMediaAudio { media, .. }) @@ -422,9 +422,10 @@ impl InputMedia { } } -impl From for InputFile { - fn from(media: InputMedia) -> InputFile { - match media { +impl InputMedia { + #[allow(dead_code)] + pub(crate) fn media(&self) -> &InputFile { + match self { InputMedia::Photo(InputMediaPhoto { media, .. }) | InputMedia::Document(InputMediaDocument { media, .. }) | InputMedia::Audio(InputMediaAudio { media, .. }) From 417b8eb1e175a9deb91f2b9b37591a7a00756dde Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 16 Feb 2021 17:10:02 +0300 Subject: [PATCH 231/755] Add `#[non_exhaustive]` on `InputFile` --- CHANGELOG.md | 1 + src/types/input_file.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07cda31b..51452b94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `#[non_exhaustive]` on `InputFile` since we may want to add new ways to send files in the future ([#49][pr49]) - `MultipartPayload` for future proofing ([#49][pr49]) - Support for `rustls` ([#24][pr24]) - `#[must_use]` attr to payloads implemented by macro ([#22][pr22]) diff --git a/src/types/input_file.rs b/src/types/input_file.rs index eb9bd600..4ace6e85 100644 --- a/src/types/input_file.rs +++ b/src/types/input_file.rs @@ -6,6 +6,7 @@ use std::{borrow::Cow, path::PathBuf}; /// /// [The official docs](https://core.telegram.org/bots/api#inputfile). #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] pub enum InputFile { File(PathBuf), Memory { From 669d6c95bed566a2c47ec67cbd647c71692053f2 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 16 Feb 2021 18:29:29 +0300 Subject: [PATCH 232/755] Make some cleanup of throttle --- CHANGELOG.md | 3 +- src/adaptors/throttle.rs | 145 +++++++++++++++++++++++---------------- 2 files changed, 87 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51452b94..3d64b17f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `RequesterExt` trait which is implemented for all `Requester`s and allows easily wrapping them in adaptors - `adaptors` module ([#14][pr14]) - `throttle`, `cache_me`, `auto_send` and `full` crate features - - Request throttling - opt-in feature represented by `Throttle` bot adapter which allows automatically checking telegram limits ([#10][pr10], [#46][pr46]) + - Request throttling - opt-in feature represented by `Throttle` bot adapter which allows automatically checking telegram limits ([#10][pr10], [#46][pr46], [#50][pr50]) - Request auto sending - ability to `.await` requests without need to call `.send()` (opt-in feature represented by `AutoSend` bot adapter, [#8][pr8]) - `get_me` caching (opt-in feature represented by `CacheMe` bot adapter) - `Requester` trait which represents bot-clients ([#7][pr7], [#12][pr12], [#27][pr27]) @@ -55,6 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [pr39]: https://github.com/teloxide/teloxide-core/pull/39 [pr46]: https://github.com/teloxide/teloxide-core/pull/46 [pr49]: https://github.com/teloxide/teloxide-core/pull/49 +[pr50]: https://github.com/teloxide/teloxide-core/pull/50 ### Changed diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index 86651891..ac27b177 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -18,7 +18,7 @@ use tokio::sync::{ use vecrem::VecExt; use crate::{ - adaptors::throttle::chan_send::{ChanSend, SendTy}, + adaptors::throttle::chan_send::{ChanSend, MpscSend}, requests::{HasPayload, Output, Request, Requester}, types::*, }; @@ -150,7 +150,7 @@ impl Default for Limits { pub struct Throttle { bot: B, // Sender is used to pass the signal to unlock by closing the channel. - queue: mpsc::Sender<(ChatIdHash, Sender)>, + queue: mpsc::Sender<(ChatIdHash, RequestLock)>, } type RequestsSent = u32; @@ -164,11 +164,11 @@ struct RequestsSentToChats { per_sec: HashMap, } -async fn worker(limits: Limits, mut rx: mpsc::Receiver<(ChatIdHash, Sender)>) { +async fn worker(limits: Limits, mut rx: mpsc::Receiver<(ChatIdHash, RequestLock)>) { // FIXME(waffle): Make an research about data structures for this queue. // Currently this is O(n) removing (n = number of elements // stayed), amortized O(1) push (vec+vecrem). - let mut queue: Vec<(ChatIdHash, Sender)> = + let mut queue: Vec<(ChatIdHash, RequestLock)> = Vec::with_capacity(limits.messages_per_sec_overall as usize); let mut history: VecDeque<(ChatIdHash, Instant)> = VecDeque::new(); @@ -268,7 +268,8 @@ async fn worker(limits: Limits, mut rx: mpsc::Receiver<(ChatIdHash, Sender)>, - queue: &mut Vec<(ChatIdHash, Sender)>, - rx_is_closed: &mut bool, -) { +async fn read_from_rx(rx: &mut mpsc::Receiver, queue: &mut Vec, rx_is_closed: &mut bool) { if queue.is_empty() { match rx.recv().await { Some(req) => queue.push(req), @@ -359,11 +356,11 @@ impl Throttle { macro_rules! f { ($m:ident $this:ident ($($arg:ident : $T:ty),*)) => { - ThrottlingRequest( - $this.inner().$m($($arg),*), - $this.queue.clone(), - |p| (&p.payload_ref().chat_id).into(), - ) + ThrottlingRequest { + request: $this.inner().$m($($arg),*), + chat_id: |p| (&p.payload_ref().chat_id).into(), + worker: $this.queue.clone(), + } }; } @@ -436,8 +433,8 @@ where C: Into, Pri: IntoIterator, { - ThrottlingRequest( - self.inner().send_invoice( + ThrottlingRequest { + request: self.inner().send_invoice( chat_id, title, description, @@ -447,9 +444,9 @@ where currency, prices, ), - self.queue.clone(), - |p| ChatIdHash::Id(p.payload_ref().chat_id as _), - ) + chat_id: |p| ChatIdHash::Id(p.payload_ref().chat_id as _), + worker: self.queue.clone(), + } } requester_forward! { @@ -506,21 +503,21 @@ impl From<&ChatId> for ChatIdHash { } } -pub struct ThrottlingRequest( - R, - mpsc::Sender<(ChatIdHash, Sender)>, - fn(&R::Payload) -> ChatIdHash, -); +pub struct ThrottlingRequest { + request: R, + chat_id: fn(&R::Payload) -> ChatIdHash, + worker: mpsc::Sender<(ChatIdHash, RequestLock)>, +} impl HasPayload for ThrottlingRequest { type Payload = R::Payload; fn payload_mut(&mut self) -> &mut Self::Payload { - self.0.payload_mut() + self.request.payload_mut() } fn payload_ref(&self) -> &Self::Payload { - self.0.payload_ref() + self.request.payload_ref() } } @@ -534,18 +531,27 @@ where fn send(self) -> Self::Send { let (tx, rx) = channel(); - let id = self.2(self.payload_ref()); - let send = self.1.send_t((id, tx)); - ThrottlingSend(ThrottlingSendInner::Registering { - request: self.0, + let tx = RequestLock(tx); + let rx = RequestWaiter(rx); + + let chat_id = (self.chat_id)(self.payload_ref()); + let send = self.worker.send1((chat_id, tx)); + + let inner = ThrottlingSendInner::Registering { + request: self.request, send, wait: rx, - }) + }; + ThrottlingSend(inner) } fn send_ref(&self) -> Self::SendRef { let (tx, rx) = channel(); - let send = self.1.clone().send_t((self.2(self.payload_ref()), tx)); + let tx = RequestLock(tx); + let rx = RequestWaiter(rx); + + let chat_id = (self.chat_id)(self.payload_ref()); + let send = self.worker.clone().send1((chat_id, tx)); // As we can't move self.0 (request) out, as we do in `send` we are // forced to call `send_ref()`. This may have overhead and/or lead to @@ -553,13 +559,14 @@ where // // However `Request` documentation explicitly notes that `send{,_ref}` // should **not** do any kind of work, so it's ok. - let request = self.0.send_ref(); + let request = self.request.send_ref(); - ThrottlingSendRef(ThrottlingSendRefInner::Registering { + let inner = ThrottlingSendRefInner::Registering { request, send, wait: rx, - }) + }; + ThrottlingSendRef(inner) } } @@ -571,13 +578,13 @@ enum ThrottlingSendInner { Registering { request: R, #[pin] - send: ChanSend, - wait: Receiver, + send: ChanSend<(ChatIdHash, RequestLock)>, + wait: RequestWaiter, }, Pending { request: R, #[pin] - wait: Receiver, + wait: RequestWaiter, }, Sent { #[pin] @@ -658,13 +665,13 @@ enum ThrottlingSendRefInner { Registering { request: R::SendRef, #[pin] - send: ChanSend, - wait: Receiver, + send: ChanSend<(ChatIdHash, RequestLock)>, + wait: RequestWaiter, }, Pending { request: R::SendRef, #[pin] - wait: Receiver, + wait: RequestWaiter, }, Sent { #[pin] @@ -736,38 +743,56 @@ impl Future for ThrottlingSendRef { } } +#[must_use] +struct RequestLock(Sender); + +impl RequestLock { + fn unlock(self) { + // Unlock request by closing oneshot channel + } +} + +#[must_use] +#[pin_project::pin_project] +struct RequestWaiter(#[pin] Receiver); + +impl Future for RequestWaiter { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + let this = self.project(); + match this.0.poll(cx) { + Poll::Ready(_) => Poll::Ready(()), + Poll::Pending => Poll::Pending, + } + } +} + mod chan_send { use std::{future::Future, pin::Pin}; use futures::task::{Context, Poll}; - use never::Never; - use tokio::sync::{mpsc, mpsc::error::SendError, oneshot::Sender}; + use tokio::sync::{mpsc, mpsc::error::SendError}; - use crate::adaptors::throttle::ChatIdHash; - - pub(super) trait SendTy { - fn send_t(self, val: (ChatIdHash, Sender)) -> ChanSend; + pub(super) trait MpscSend { + fn send1(self, val: T) -> ChanSend; } #[pin_project::pin_project] - pub(super) struct ChanSend(#[pin] Inner); + pub(super) struct ChanSend(#[pin] Inner); #[cfg(not(feature = "nightly"))] - type Inner = - Pin)>>> + Send>>; + type Inner = Pin>> + Send>>; #[cfg(feature = "nightly")] - type Inner = impl Future)>>>; + type Inner = impl Future>>; - impl SendTy for mpsc::Sender<(ChatIdHash, Sender)> { + impl MpscSend for mpsc::Sender { // `return`s trick IDEA not to show errors #[allow(clippy::needless_return)] - fn send_t(self, val: (ChatIdHash, Sender)) -> ChanSend { + fn send1(self, val: T) -> ChanSend { #[cfg(feature = "nightly")] { - fn def( - sender: mpsc::Sender<(ChatIdHash, Sender)>, - val: (ChatIdHash, Sender), - ) -> Inner { + fn def(sender: mpsc::Sender, val: T) -> Inner { async move { sender.send(val).await } } return ChanSend(def(self, val)); @@ -780,8 +805,8 @@ mod chan_send { } } - impl Future for ChanSend { - type Output = Result<(), SendError<(ChatIdHash, Sender)>>; + impl Future for ChanSend { + type Output = Result<(), SendError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.project().0.poll(cx) From 72306c39d977baca503547670fd9b6b470cb1730 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 16 Feb 2021 18:49:18 +0300 Subject: [PATCH 233/755] Limit queue growth in throttle --- src/adaptors/throttle.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index ac27b177..26bdee77 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -178,6 +178,7 @@ async fn worker(limits: Limits, mut rx: mpsc::Receiver<(ChatIdHash, RequestLock) while !rx_is_closed || !queue.is_empty() { read_from_rx(&mut rx, &mut queue, &mut rx_is_closed).await; + debug_assert_eq!(queue.capacity(), limits.messages_per_sec_overall as usize); // _Maybe_ we need to use `spawn_blocking` here, because there is // decent amount of blocking work. However _for now_ I've decided not @@ -294,7 +295,8 @@ async fn read_from_rx(rx: &mut mpsc::Receiver, queue: &mut Vec, rx_is_c } } - loop { + // Don't grow queue bigger than the capacity to limit DOS posibility + while queue.len() < queue.capacity() { // FIXME(waffle): https://github.com/tokio-rs/tokio/issues/3350 match rx.recv().now_or_never() { Some(Some(req)) => queue.push(req), From 80329f8a1ee08fb8efc1ec1a72ad304b39a2226b Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 16 Feb 2021 19:07:50 +0300 Subject: [PATCH 234/755] Throttle: make comment up to date & add channel fn --- src/adaptors/throttle.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index 26bdee77..c431611d 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -13,7 +13,7 @@ use futures::{ use never::Never; use tokio::sync::{ mpsc, - oneshot::{channel, Receiver, Sender}, + oneshot::{self, Receiver, Sender}, }; use vecrem::VecExt; @@ -129,7 +129,6 @@ impl Default for Limits { /// ```no_run (throttle fails to spawn task without tokio runtime) /// use teloxide_core::{adaptors::throttle::Limits, requests::RequesterExt, Bot}; /// -/// # #[allow(deprecated)] /// let bot = Bot::new("TOKEN").throttle(Limits::default()); /// /// /* send many requests here */ @@ -149,7 +148,7 @@ impl Default for Limits { /// wrapper. pub struct Throttle { bot: B, - // Sender is used to pass the signal to unlock by closing the channel. + // `RequestLock` allows to unlock requests (allowing them to be sent). queue: mpsc::Sender<(ChatIdHash, RequestLock)>, } @@ -533,8 +532,6 @@ where fn send(self) -> Self::Send { let (tx, rx) = channel(); - let tx = RequestLock(tx); - let rx = RequestWaiter(rx); let chat_id = (self.chat_id)(self.payload_ref()); let send = self.worker.send1((chat_id, tx)); @@ -549,8 +546,6 @@ where fn send_ref(&self) -> Self::SendRef { let (tx, rx) = channel(); - let tx = RequestLock(tx); - let rx = RequestWaiter(rx); let chat_id = (self.chat_id)(self.payload_ref()); let send = self.worker.clone().send1((chat_id, tx)); @@ -745,6 +740,13 @@ impl Future for ThrottlingSendRef { } } +fn channel() -> (RequestLock, RequestWaiter) { + let (tx, rx) = oneshot::channel(); + let tx = RequestLock(tx); + let rx = RequestWaiter(rx); + (tx, rx) +} + #[must_use] struct RequestLock(Sender); From 2f78da091ad57d9f291535639b2b2f1718b82c80 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 17 Feb 2021 00:06:37 +0300 Subject: [PATCH 235/755] fill fields in Cargo.toml before releasing the lib --- Cargo.toml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 2e6240fc..ebe550c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "teloxide-core" +description = "Core part of the `teloxide` library - telegram bot API client" version = "0.1.0" edition = "2018" authors = [ @@ -13,6 +14,20 @@ authors = [ "Alexey Fedechkin " ] +license = "MIT" +repository = "https://github.com/teloxide/teloxide-core/" +homepage = "https://github.com/teloxide/teloxide-core/" +documentation = "https://docs.rs/teloxide-core/" +readme = "README.md" + +keywords = ["telegram", "bot", "tba"] +categories = ["API bindings", "Asynchronous"] + +exclude = [ + ".github/*", + "netlify.toml", +] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] From 6b9e278578d0f90eca52c8d04e8aa8caa8133916 Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 17 Feb 2021 00:17:14 +0300 Subject: [PATCH 236/755] fix crates.io categories --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ebe550c5..db36dd25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ documentation = "https://docs.rs/teloxide-core/" readme = "README.md" keywords = ["telegram", "bot", "tba"] -categories = ["API bindings", "Asynchronous"] +categories = ["api-bindings", "asynchronous"] exclude = [ ".github/*", From 7c18750a8f15b30d8fedec6f30a61c1ce5b2015a Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 17 Feb 2021 14:23:13 +0300 Subject: [PATCH 237/755] Remove dbg! print --- Cargo.toml | 2 +- src/net/request.rs | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ebe550c5..ea38f146 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "teloxide-core" description = "Core part of the `teloxide` library - telegram bot API client" -version = "0.1.0" +version = "0.1.1" edition = "2018" authors = [ "Temirkhan Myrzamadi ", diff --git a/src/net/request.rs b/src/net/request.rs index 1ab76873..b8726001 100644 --- a/src/net/request.rs +++ b/src/net/request.rs @@ -59,10 +59,9 @@ where tokio::time::sleep(DELAY_ON_SERVER_ERROR).await; } - serde_json::from_str::>(dbg!(&response - .text() - .await - .map_err(RequestError::NetworkError)?)) + serde_json::from_str::>( + &response.text().await.map_err(RequestError::NetworkError)?, + ) .map_err(RequestError::InvalidJson)? .into() } From a61d81d829d4770ad58df4f63d804279f06c037b Mon Sep 17 00:00:00 2001 From: Waffle Date: Wed, 17 Feb 2021 16:04:04 +0300 Subject: [PATCH 238/755] Update changelog --- CHANGELOG.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d64b17f..62aebd43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] + +## [0.1.1] - 2020-02-17 + +### Fixed + +- Remove `dbg!` call from internals ([#53][pr53]) + +[pr53]: https://github.com/teloxide/teloxide-core/pull/53 + +## [0.1.0] - 2020-02-17 + ### Added - `#[non_exhaustive]` on `InputFile` since we may want to add new ways to send files in the future ([#49][pr49]) @@ -45,8 +56,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [pr7]: https://github.com/teloxide/teloxide-core/pull/7 [pr8]: https://github.com/teloxide/teloxide-core/pull/8 [pr10]: https://github.com/teloxide/teloxide-core/pull/10 -[pr12]: https://github.com/teloxide/teloxide-core/pull/10 -[pr14]: https://github.com/teloxide/teloxide-core/pull/10 +[pr12]: https://github.com/teloxide/teloxide-core/pull/12 +[pr14]: https://github.com/teloxide/teloxide-core/pull/14 [pr22]: https://github.com/teloxide/teloxide-core/pull/22 [pr24]: https://github.com/teloxide/teloxide-core/pull/24 [pr26]: https://github.com/teloxide/teloxide-core/pull/26 From 539d7758f57045e493f2a0d01ef20fcb6c5276d6 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 22 Feb 2021 07:41:33 +0600 Subject: [PATCH 239/755] Centralise README.md --- README.md | 49 ++++++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 0e01f9b1..88a49a07 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,37 @@

+ +

teloxide-core

+
+ + + + + + + + + + + + + + + + + + + + + + The core part of [teloxide] providing tools for making requests to the [Telegram Bot API] with ease. This library is fully asynchronous and built using [tokio].
-# teloxide-core - -[![CI status](https://github.com/teloxide/teloxide-core/workflows/Continuous%20integration/badge.svg)](https://github.com/teloxide/teloxide-core/actions) -[![documentation](https://docs.rs/teloxide_core/badge.svg)](https://docs.rs/teloxide_core/) -[![documentation (master)](https://img.shields.io/badge/docs-master-blue)](https://teloxide-core.netlify.com) -[![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) -[![Api Cov](https://img.shields.io/badge/API%20coverage-Up%20to%200.4.9%20(inclusively)-green.svg)](https://core.telegram.org/bots/api) -[![crates.io](https://img.shields.io/crates/v/teloxide_core.svg)](https://crates.io/crates/teloxide_core) -[![Official Chat](https://img.shields.io/badge/official%20chat-t.me%2Fteloxide-blueviolet)](https://t.me/teloxide) - - - -Core part of the [`teloxide`] library. - -This library provides tools for making requests to the [Telegram Bot API] -(Currently, version `4.9` is supported) with ease. The library is fully -asynchronouns and built using [`tokio`]. - ```toml teloxide_core = "0.1" ``` -_Compiler support: requires rustc 1.49+_ +_Compiler support: requires rustc 1.49+_. -[`teloxide`]: https://docs.rs/teloxide +[teloxide]: https://docs.rs/teloxide [Telegram Bot API]: https://core.telegram.org/bots/api -[`tokio`]: https://tokio.rs \ No newline at end of file +[tokio]: https://tokio.rs From dcdba782856ddb88558b782392dd26d3cdf2910c Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 22 Feb 2021 08:47:50 +0300 Subject: [PATCH 240/755] Fix send_contact singnature - `phone_number: f64` -> `phone_number: String` - `first_name: f64` -> `first_name: String` --- CHANGELOG.md | 5 +++++ src/bot/api.rs | 4 +++- src/local_macros.rs | 8 +++++--- src/payloads/send_contact.rs | 6 +++--- src/requests/requester.rs | 13 ++++++++++--- 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62aebd43..a6479ddc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +### Fixed + +- `send_contact` signature (`phone_number` and `first_name` `f64` => `String`) ([#56][pr56]) + +[pr56]: https://github.com/teloxide/teloxide-core/pull/56 ## [0.1.1] - 2020-02-17 diff --git a/src/bot/api.rs b/src/bot/api.rs index 86997994..0cbcf24f 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -267,9 +267,11 @@ impl Requester for Bot { type SendContact = JsonRequest; - fn send_contact(&self, chat_id: C, phone_number: f64, first_name: f64) -> Self::SendContact + fn send_contact(&self, chat_id: C, phone_number: P, first_name: F) -> Self::SendContact where C: Into, + P: Into, + F: Into, { Self::SendContact::new( self.clone(), diff --git a/src/local_macros.rs b/src/local_macros.rs index 1129e983..357e17b9 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -393,7 +393,7 @@ macro_rules! impl_payload { } #[macro_use] -// This macro is auto generated by `cg` (be02d84). +// This macro is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS MACRO**, // edit `cg` instead. macro_rules! requester_forward { @@ -582,9 +582,11 @@ macro_rules! requester_forward { (@method send_contact $body:ident $ty:ident) => { type SendContact = $ty![SendContact]; - fn send_contact(&self, chat_id: C, phone_number: f64, first_name: f64) -> Self::SendContact where C: Into { + fn send_contact(&self, chat_id: C, phone_number: P, first_name: F) -> Self::SendContact where C: Into, + P: Into, + F: Into { let this = self; - $body!(send_contact this (chat_id: C, phone_number: f64, first_name: f64)) + $body!(send_contact this (chat_id: C, phone_number: P, first_name: F)) } }; (@method send_poll $body:ident $ty:ident) => { diff --git a/src/payloads/send_contact.rs b/src/payloads/send_contact.rs index 215cd02f..f6b464fc 100644 --- a/src/payloads/send_contact.rs +++ b/src/payloads/send_contact.rs @@ -9,15 +9,15 @@ impl_payload! { /// Use this method to send phone contacts. On success, the sent [`Message`] is returned. /// /// [`Message`]: crate::types::Message - #[derive(Debug, PartialEq, Clone, Serialize)] + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] pub SendContact (SendContactSetters) => Message { required { /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) pub chat_id: ChatId [into], /// Contact's phone number - pub phone_number: f64, + pub phone_number: String [into], /// Contact's first name - pub first_name: f64, + pub first_name: String [into], } optional { /// Contact's last name diff --git a/src/requests/requester.rs b/src/requests/requester.rs index 68ea02b9..0335ca02 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -53,7 +53,7 @@ pub trait Requester { /// Error type returned by all requests. type Err: std::error::Error + Send; - // This block is auto generated by `cg` (be02d84). + // This block is auto generated by `cg` (8ee7ef2). // **DO NOT EDIT THIS BLOCK**, // edit `cg` instead. @@ -251,9 +251,16 @@ pub trait Requester { type SendContact: Request; /// For Telegram documentation see [`SendContact`]. - fn send_contact(&self, chat_id: C, phone_number: f64, first_name: f64) -> Self::SendContact + fn send_contact( + &self, + chat_id: C, + phone_number: P, + first_name: F, + ) -> Self::SendContact where - C: Into; + C: Into, + P: Into, + F: Into; type SendPoll: Request; From 7025de58f56357283280aec0e6fcdb648bf46034 Mon Sep 17 00:00:00 2001 From: Waffle Date: Mon, 22 Feb 2021 11:30:13 +0300 Subject: [PATCH 241/755] Fix typos in payloads - `get_updates`: `offset` `i64` -> `i32` - `send_location`: make `live_period` optional --- CHANGELOG.md | 4 ++++ src/bot/api.rs | 10 ++-------- src/local_macros.rs | 4 ++-- src/payloads/get_updates.rs | 2 +- src/payloads/send_location.rs | 4 ++-- src/requests/requester.rs | 8 +------- 6 files changed, 12 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6479ddc..5cf4b84a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Fix typos in payloads ([#57][pr57]): + - `get_updates`: `offset` `i64` -> `i32` + - `send_location`: make `live_period` optional - `send_contact` signature (`phone_number` and `first_name` `f64` => `String`) ([#56][pr56]) [pr56]: https://github.com/teloxide/teloxide-core/pull/56 +[pr57]: https://github.com/teloxide/teloxide-core/pull/57 ## [0.1.1] - 2020-02-17 diff --git a/src/bot/api.rs b/src/bot/api.rs index 0cbcf24f..6ee44da9 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -158,19 +158,13 @@ impl Requester for Bot { type SendLocation = JsonRequest; - fn send_location( - &self, - chat_id: C, - latitude: f64, - longitude: f64, - live_period: u32, - ) -> Self::SendLocation + fn send_location(&self, chat_id: C, latitude: f64, longitude: f64) -> Self::SendLocation where C: Into, { Self::SendLocation::new( self.clone(), - payloads::SendLocation::new(chat_id, latitude, longitude, live_period), + payloads::SendLocation::new(chat_id, latitude, longitude), ) } diff --git a/src/local_macros.rs b/src/local_macros.rs index 357e17b9..bf743779 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -532,9 +532,9 @@ macro_rules! requester_forward { (@method send_location $body:ident $ty:ident) => { type SendLocation = $ty![SendLocation]; - fn send_location(&self, chat_id: C, latitude: f64, longitude: f64, live_period: u32) -> Self::SendLocation where C: Into { + fn send_location(&self, chat_id: C, latitude: f64, longitude: f64) -> Self::SendLocation where C: Into { let this = self; - $body!(send_location this (chat_id: C, latitude: f64, longitude: f64, live_period: u32)) + $body!(send_location this (chat_id: C, latitude: f64, longitude: f64)) } }; (@method edit_message_live_location $body:ident $ty:ident) => { diff --git a/src/payloads/get_updates.rs b/src/payloads/get_updates.rs index beaec909..df63aa63 100644 --- a/src/payloads/get_updates.rs +++ b/src/payloads/get_updates.rs @@ -17,7 +17,7 @@ impl_payload! { /// Identifier of the first update to be returned. Must be greater by one than the highest among the identifiers of previously received updates. By default, updates starting with the earliest unconfirmed update are returned. An update is considered confirmed as soon as [`GetUpdates`] is called with an offset higher than its update_id. The negative offset can be specified to retrieve updates starting from -offset update from the end of the updates queue. All previous updates will forgotten. /// /// [`GetUpdates`]: crate::payloads::GetUpdates - pub offset: i64, + pub offset: i32, /// Limits the number of updates to be retrieved. Values between 1-100 are accepted. Defaults to 100. pub limit: u8, /// Timeout in seconds for long polling. Defaults to 0, i.e. usual short polling. Should be positive, short polling should be used for testing purposes only. diff --git a/src/payloads/send_location.rs b/src/payloads/send_location.rs index f95cc19e..d9cbbd6e 100644 --- a/src/payloads/send_location.rs +++ b/src/payloads/send_location.rs @@ -18,12 +18,12 @@ impl_payload! { pub latitude: f64, /// Longitude of the location pub longitude: f64, + } + optional { /// Period in seconds for which the location will be updated (see [Live Locations], should be between 60 and 86400. /// /// [Live Locations]: https://telegram.org/blog/live-locations pub live_period: u32, - } - optional { /// Sends the message [silently]. Users will receive a notification with no sound. /// /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages diff --git a/src/requests/requester.rs b/src/requests/requester.rs index 0335ca02..69a79199 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -166,13 +166,7 @@ pub trait Requester { type SendLocation: Request; /// For Telegram documentation see [`SendLocation`]. - fn send_location( - &self, - chat_id: C, - latitude: f64, - longitude: f64, - live_period: u32, - ) -> Self::SendLocation + fn send_location(&self, chat_id: C, latitude: f64, longitude: f64) -> Self::SendLocation where C: Into; From a00678b81c51814d6c269cab6a16ed841f274359 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Mon, 22 Feb 2021 14:36:30 +0600 Subject: [PATCH 242/755] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 88a49a07..472a36ac 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ - The core part of [teloxide] providing tools for making requests to the [Telegram Bot API] with ease. This library is fully asynchronous and built using [tokio]. + The core part of [`teloxide`] providing tools for making requests to the [Telegram Bot API] with ease. This library is fully asynchronous and built using [`tokio`]. ```toml @@ -32,6 +32,6 @@ teloxide_core = "0.1" ``` _Compiler support: requires rustc 1.49+_. -[teloxide]: https://docs.rs/teloxide +[`teloxide`]: https://docs.rs/teloxide [Telegram Bot API]: https://core.telegram.org/bots/api -[tokio]: https://tokio.rs +[`tokio`]: https://tokio.rs From 7e43c4d32168aa7b34086e17f9df3b612533f103 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 27 Feb 2021 01:07:57 +0300 Subject: [PATCH 243/755] Add GetUpdatesFaultTolerant --- CHANGELOG.md | 6 ++++++ src/adaptors/auto_send.rs | 3 ++- src/adaptors/cache_me.rs | 2 +- src/adaptors/parse_mode.rs | 2 +- src/adaptors/throttle.rs | 2 +- src/bot/api.rs | 9 +++++++++ src/local_macros.rs | 12 +++++++++--- src/payloads.rs | 6 ++++++ src/payloads/get_updates_fault_tolerant.rs | 18 ++++++++++++++++++ src/requests/requester.rs | 10 ++++++++-- 10 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 src/payloads/get_updates_fault_tolerant.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cf4b84a..ad248230 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +### Added + +- `GetUpdatesFaultTolerant` - fault toletant version of `GetUpdates` ([#58][pr58]) (**BC**) + +[pr58]: https://github.com/teloxide/teloxide-core/pull/58 + ### Fixed - Fix typos in payloads ([#57][pr57]): diff --git a/src/adaptors/auto_send.rs b/src/adaptors/auto_send.rs index 3cacee42..f091eae8 100644 --- a/src/adaptors/auto_send.rs +++ b/src/adaptors/auto_send.rs @@ -101,7 +101,8 @@ where add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, set_sticker_set_thumb, send_invoice, answer_shipping_query, answer_pre_checkout_query, set_passport_data_errors, send_game, - set_game_score, set_game_score_inline, get_game_high_scores => f, fty + set_game_score, set_game_score_inline, get_game_high_scores, + get_updates_fault_tolerant => f, fty } } diff --git a/src/adaptors/cache_me.rs b/src/adaptors/cache_me.rs index 541c9d42..f964f6a4 100644 --- a/src/adaptors/cache_me.rs +++ b/src/adaptors/cache_me.rs @@ -118,7 +118,7 @@ where add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, set_sticker_set_thumb, send_invoice, answer_shipping_query, answer_pre_checkout_query, set_passport_data_errors, send_game, - set_game_score, set_game_score_inline, get_game_high_scores => f, fty + set_game_score, set_game_score_inline, get_game_high_scores, get_updates_fault_tolerant => f, fty } } diff --git a/src/adaptors/parse_mode.rs b/src/adaptors/parse_mode.rs index f5802d2d..f35cd3b2 100644 --- a/src/adaptors/parse_mode.rs +++ b/src/adaptors/parse_mode.rs @@ -112,7 +112,7 @@ impl Requester for DefaultParseMode { add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, set_sticker_set_thumb, send_invoice, answer_shipping_query, answer_pre_checkout_query, set_passport_data_errors, send_game, set_game_score, - set_game_score_inline, get_game_high_scores => fid, fty + set_game_score_inline, get_game_high_scores, get_updates_fault_tolerant => fid, fty } } diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index c431611d..efdda09f 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -469,7 +469,7 @@ where add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, set_sticker_set_thumb, answer_shipping_query, answer_pre_checkout_query, set_passport_data_errors, send_game, set_game_score, set_game_score_inline, - get_game_high_scores => fid, ftyid + get_game_high_scores, get_updates_fault_tolerant => fid, ftyid } } diff --git a/src/bot/api.rs b/src/bot/api.rs index 6ee44da9..7abb02ff 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -970,4 +970,13 @@ impl Requester for Bot { payloads::GetGameHighScores::new(user_id, target), ) } + + type GetUpdatesFaultTolerant = JsonRequest; + + fn get_updates_fault_tolerant(&self) -> Self::GetUpdatesFaultTolerant { + Self::GetUpdatesFaultTolerant::new( + self.clone(), + payloads::GetUpdatesFaultTolerant(payloads::GetUpdates::new()), + ) + } } diff --git a/src/local_macros.rs b/src/local_macros.rs index bf743779..9c7fd33f 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -393,7 +393,7 @@ macro_rules! impl_payload { } #[macro_use] -// This macro is auto generated by `cg` (8ee7ef2). +// This macro is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS MACRO**, // edit `cg` instead. macro_rules! requester_forward { @@ -403,8 +403,6 @@ macro_rules! requester_forward { requester_forward!(@method $rest $body $ty); )* }; - - (@method get_updates $body:ident $ty:ident) => { type GetUpdates = $ty![GetUpdates]; @@ -1046,6 +1044,14 @@ macro_rules! requester_forward { $body!(get_game_high_scores this (user_id: u32, target: T)) } }; + (@method get_updates_fault_tolerant $body:ident $ty:ident) => { + type GetUpdatesFaultTolerant = $ty![GetUpdatesFaultTolerant]; + + fn get_updates_fault_tolerant(&self) -> Self::GetUpdatesFaultTolerant { + let this = self; + $body!(get_updates_fault_tolerant this ()) + } + }; } #[macro_use] diff --git a/src/payloads.rs b/src/payloads.rs index cf31ea9d..685fafba 100644 --- a/src/payloads.rs +++ b/src/payloads.rs @@ -178,3 +178,9 @@ pub use stop_poll::{StopPoll, StopPollSetters}; pub use unban_chat_member::{UnbanChatMember, UnbanChatMemberSetters}; pub use unpin_chat_message::{UnpinChatMessage, UnpinChatMessageSetters}; pub use upload_sticker_file::{UploadStickerFile, UploadStickerFileSetters}; + +// end of auto generated block + +mod get_updates_fault_tolerant; + +pub use get_updates_fault_tolerant::GetUpdatesFaultTolerant; diff --git a/src/payloads/get_updates_fault_tolerant.rs b/src/payloads/get_updates_fault_tolerant.rs new file mode 100644 index 00000000..6473815c --- /dev/null +++ b/src/payloads/get_updates_fault_tolerant.rs @@ -0,0 +1,18 @@ +use serde::Serialize; + +use crate::{ + payloads::GetUpdates, + requests::Payload, + types::{NonStrictVec, Update}, +}; + +/// Fault tollerant version of [`GetUpdates`]. +#[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Serialize)] +#[serde(transparent)] +pub struct GetUpdatesFaultTolerant(pub GetUpdates); + +impl Payload for GetUpdatesFaultTolerant { + type Output = NonStrictVec; + + const NAME: &'static str = GetUpdates::NAME; +} diff --git a/src/requests/requester.rs b/src/requests/requester.rs index 69a79199..336f2c3c 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -53,7 +53,7 @@ pub trait Requester { /// Error type returned by all requests. type Err: std::error::Error + Send; - // This block is auto generated by `cg` (8ee7ef2). + // This block is auto generated by `cg` (dec2d73). // **DO NOT EDIT THIS BLOCK**, // edit `cg` instead. @@ -749,6 +749,11 @@ pub trait Requester { fn get_game_high_scores(&self, user_id: u32, target: T) -> Self::GetGameHighScores where T: Into; + + type GetUpdatesFaultTolerant: Request; + + /// For Telegram documentation see [`GetUpdatesFaultTolerant`]. + fn get_updates_fault_tolerant(&self) -> Self::GetUpdatesFaultTolerant; } macro_rules! fty { @@ -786,7 +791,8 @@ macro_rules! forward_all { add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, set_sticker_set_thumb, send_invoice, answer_shipping_query, answer_pre_checkout_query, set_passport_data_errors, send_game, - set_game_score, set_game_score_inline, get_game_high_scores => fwd_deref, fty + set_game_score, set_game_score_inline, get_game_high_scores, + get_updates_fault_tolerant => fwd_deref, fty } }; } From 466dbe6f8dcd6200d241da4508390a060c6717c7 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 27 Feb 2021 18:38:02 +0600 Subject: [PATCH 244/755] Fix a documentation typo --- src/payloads/get_updates_fault_tolerant.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/payloads/get_updates_fault_tolerant.rs b/src/payloads/get_updates_fault_tolerant.rs index 6473815c..b32d6f00 100644 --- a/src/payloads/get_updates_fault_tolerant.rs +++ b/src/payloads/get_updates_fault_tolerant.rs @@ -6,7 +6,7 @@ use crate::{ types::{NonStrictVec, Update}, }; -/// Fault tollerant version of [`GetUpdates`]. +/// The fault tolerant version of [`GetUpdates`]. #[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Serialize)] #[serde(transparent)] pub struct GetUpdatesFaultTolerant(pub GetUpdates); From c2aaa724f4fc6512e93edec12860907540eeefce Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 27 Feb 2021 18:52:11 +0600 Subject: [PATCH 245/755] NonStrictVec -> SemiparsedVec --- CHANGELOG.md | 4 ++++ src/payloads/get_updates_fault_tolerant.rs | 4 ++-- src/types/non_telegram_types/non_strict_vec.rs | 10 ++++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad248230..85f6ba61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [pr56]: https://github.com/teloxide/teloxide-core/pull/56 [pr57]: https://github.com/teloxide/teloxide-core/pull/57 +### Changed + + - `NonStrictVec` -> `SemiparsedVec`. + ## [0.1.1] - 2020-02-17 ### Fixed diff --git a/src/payloads/get_updates_fault_tolerant.rs b/src/payloads/get_updates_fault_tolerant.rs index b32d6f00..1d3276e0 100644 --- a/src/payloads/get_updates_fault_tolerant.rs +++ b/src/payloads/get_updates_fault_tolerant.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::{ payloads::GetUpdates, requests::Payload, - types::{NonStrictVec, Update}, + types::{SemiparsedVec, Update}, }; /// The fault tolerant version of [`GetUpdates`]. @@ -12,7 +12,7 @@ use crate::{ pub struct GetUpdatesFaultTolerant(pub GetUpdates); impl Payload for GetUpdatesFaultTolerant { - type Output = NonStrictVec; + type Output = SemiparsedVec; const NAME: &'static str = GetUpdates::NAME; } diff --git a/src/types/non_telegram_types/non_strict_vec.rs b/src/types/non_telegram_types/non_strict_vec.rs index 37bc46d7..6d2e5916 100644 --- a/src/types/non_telegram_types/non_strict_vec.rs +++ b/src/types/non_telegram_types/non_strict_vec.rs @@ -1,14 +1,16 @@ use serde::de::DeserializeOwned; use serde_json::{from_value, Value}; +/// A vector of possibly unparsed JSON objects. +/// /// Similar to `Vec` but if it fails to deserialize element, it just saves -/// `Err((value, err))` +/// `Err((serde_json::Value, serde_json::Error))`. #[derive(Debug, serde::Deserialize)] #[serde(from = "Vec")] #[serde(bound = "T: DeserializeOwned")] -pub struct NonStrictVec(pub Vec>); +pub struct SemiparsedVec(pub Vec>); -impl From> for NonStrictVec { +impl From> for SemiparsedVec { fn from(vec: Vec) -> Self { Self( vec.into_iter() @@ -22,7 +24,7 @@ impl From> for NonStrictVec { fn test() { use crate::types::Update; - let x: NonStrictVec = serde_json::from_str( + let x: SemiparsedVec = serde_json::from_str( r#"[{ "update_id": 923808447, "message": { From b465da5f1650893cc033d995343858371505eaf1 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sun, 28 Feb 2021 22:32:50 +0600 Subject: [PATCH 246/755] Derive Clone for AutoSend --- CHANGELOG.md | 1 + src/adaptors/auto_send.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85f6ba61..77610d3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `GetUpdatesFaultTolerant` - fault toletant version of `GetUpdates` ([#58][pr58]) (**BC**) +- Derive `Clone` for `AutoSend`. [pr58]: https://github.com/teloxide/teloxide-core/pull/58 diff --git a/src/adaptors/auto_send.rs b/src/adaptors/auto_send.rs index f091eae8..d6a7f44c 100644 --- a/src/adaptors/auto_send.rs +++ b/src/adaptors/auto_send.rs @@ -37,6 +37,7 @@ use crate::{ /// let myself: Me = bot.get_me().await?; // No .send()! /// # Ok::<_, teloxide_core::RequestError>(()) }; /// ``` +#[derive(Clone)] pub struct AutoSend { bot: B, } From 6fc7e7d80403d08d2705aec40fe8104d67755972 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 6 Mar 2021 03:07:35 +0600 Subject: [PATCH 247/755] Adjust with the latest teloxide --- rustfmt.toml | 2 +- src/lib.rs | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index 1907f64f..aba42ed2 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -2,6 +2,6 @@ format_code_in_doc_comments = true wrap_comments = true format_strings = true max_width = 80 -merge_imports = true +imports_granularity = "Crate" use_small_heuristics = "Max" use_field_init_shorthand = true diff --git a/src/lib.rs b/src/lib.rs index 5744c436..f636d6fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,6 +77,13 @@ pub fn teloxide(attr: TokenStream, item: TokenStream) -> TokenStream { type>" ), }; + let requester_param_type = match params + .get(1) + .expect("A requester parameter must be specified") + { + FnArg::Typed(typed) => typed.ty.clone(), + _ => unreachable!(), + }; let aux_param_type = match params.get(2) { Some(data_param_type) => match *data_param_type { FnArg::Typed(typed) => typed.ty.clone(), @@ -101,8 +108,9 @@ pub fn teloxide(attr: TokenStream, item: TokenStream) -> TokenStream { type Aux = #aux_param_type; type Dialogue = <#fn_return_type as teloxide::dispatching::dialogue::SubtransitionOutputType>::Output; type Error = <#fn_return_type as teloxide::dispatching::dialogue::SubtransitionOutputType>::Error; + type Requester = <#requester_param_type as teloxide::dispatching::UpdateWithCxRequesterType>::Requester; - fn react(self, cx: teloxide::dispatching::dialogue::TransitionIn, aux: #aux_param_type) + fn react(self, cx: teloxide::dispatching::dialogue::TransitionIn, aux: #aux_param_type) -> futures::future::BoxFuture<'static, #fn_return_type> { #item futures::future::FutureExt::boxed(#call_fn) @@ -152,8 +160,11 @@ pub fn derive_transition(item: TokenStream) -> TokenStream { "impl teloxide::dispatching::dialogue::Transition for {1} {{type Aux \ = <{0} as teloxide::dispatching::dialogue::Subtransition>::Aux;type \ Error = <{0} as \ - teloxide::dispatching::dialogue::Subtransition>::Error;fn \ - react(self, cx: teloxide::dispatching::dialogue::TransitionIn, aux: \ + teloxide::dispatching::dialogue::Subtransition>::Error;type \ + Requester = <{0} as \ + teloxide::dispatching::dialogue::Subtransition>::Requester;fn \ + react(self, cx: \ + teloxide::dispatching::dialogue::TransitionIn, aux: \ Self::Aux) -> futures::future::BoxFuture<'static, \ teloxide::dispatching::dialogue::TransitionOut> \ {{ futures::future::FutureExt::boxed(async move {{ match self {{", From 7600a88312c49d4235275a5225993ecc2e98a791 Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 6 Mar 2021 00:39:19 +0300 Subject: [PATCH 248/755] Fix SetWebhook Make `GetUpdates::allowed_updates` optional --- CHANGELOG.md | 2 ++ src/bot/api.rs | 8 ++------ src/local_macros.rs | 9 +++------ src/payloads/add_sticker_to_set.rs | 2 +- src/payloads/answer_callback_query.rs | 2 +- src/payloads/answer_inline_query.rs | 2 +- src/payloads/answer_pre_checkout_query.rs | 2 +- src/payloads/answer_shipping_query.rs | 2 +- src/payloads/create_new_sticker_set.rs | 2 +- src/payloads/delete_chat_photo.rs | 2 +- src/payloads/delete_chat_sticker_set.rs | 2 +- src/payloads/delete_message.rs | 2 +- src/payloads/delete_sticker_from_set.rs | 2 +- src/payloads/delete_webhook.rs | 2 +- src/payloads/edit_message_caption.rs | 2 +- src/payloads/edit_message_caption_inline.rs | 2 +- src/payloads/edit_message_live_location.rs | 2 +- src/payloads/edit_message_live_location_inline.rs | 2 +- src/payloads/edit_message_media.rs | 2 +- src/payloads/edit_message_media_inline.rs | 2 +- src/payloads/edit_message_reply_markup.rs | 2 +- src/payloads/edit_message_reply_markup_inline.rs | 2 +- src/payloads/edit_message_text.rs | 2 +- src/payloads/edit_message_text_inline.rs | 2 +- src/payloads/export_chat_invite_link.rs | 2 +- src/payloads/forward_message.rs | 2 +- src/payloads/get_chat.rs | 2 +- src/payloads/get_chat_administrators.rs | 2 +- src/payloads/get_chat_member.rs | 2 +- src/payloads/get_chat_members_count.rs | 2 +- src/payloads/get_file.rs | 2 +- src/payloads/get_game_high_scores.rs | 2 +- src/payloads/get_me.rs | 2 +- src/payloads/get_my_commands.rs | 2 +- src/payloads/get_sticker_set.rs | 2 +- src/payloads/get_updates.rs | 2 +- src/payloads/get_user_profile_photos.rs | 2 +- src/payloads/get_webhook_info.rs | 2 +- src/payloads/kick_chat_member.rs | 2 +- src/payloads/leave_chat.rs | 2 +- src/payloads/pin_chat_message.rs | 2 +- src/payloads/promote_chat_member.rs | 2 +- src/payloads/restrict_chat_member.rs | 2 +- src/payloads/send_animation.rs | 2 +- src/payloads/send_audio.rs | 2 +- src/payloads/send_chat_action.rs | 2 +- src/payloads/send_contact.rs | 2 +- src/payloads/send_dice.rs | 2 +- src/payloads/send_document.rs | 2 +- src/payloads/send_game.rs | 2 +- src/payloads/send_invoice.rs | 2 +- src/payloads/send_location.rs | 2 +- src/payloads/send_media_group.rs | 2 +- src/payloads/send_message.rs | 2 +- src/payloads/send_photo.rs | 2 +- src/payloads/send_poll.rs | 2 +- src/payloads/send_sticker.rs | 2 +- src/payloads/send_venue.rs | 2 +- src/payloads/send_video.rs | 2 +- src/payloads/send_video_note.rs | 2 +- src/payloads/send_voice.rs | 2 +- .../set_chat_administrator_custom_title.rs | 2 +- src/payloads/set_chat_description.rs | 2 +- src/payloads/set_chat_permissions.rs | 2 +- src/payloads/set_chat_photo.rs | 2 +- src/payloads/set_chat_sticker_set.rs | 2 +- src/payloads/set_chat_title.rs | 2 +- src/payloads/set_game_score.rs | 2 +- src/payloads/set_game_score_inline.rs | 2 +- src/payloads/set_my_commands.rs | 2 +- src/payloads/set_passport_data_errors.rs | 2 +- src/payloads/set_sticker_position_in_set.rs | 2 +- src/payloads/set_sticker_set_thumb.rs | 2 +- src/payloads/set_webhook.rs | 14 +++++++------- src/payloads/stop_message_live_location.rs | 2 +- src/payloads/stop_message_live_location_inline.rs | 2 +- src/payloads/stop_poll.rs | 2 +- src/payloads/unban_chat_member.rs | 2 +- src/payloads/unpin_chat_message.rs | 2 +- src/payloads/upload_sticker_file.rs | 2 +- src/requests/requester.rs | 7 +++---- 81 files changed, 93 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cf4b84a..68ddd40f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- `set_webhook` signature (make `allowed_updates` optional) ([#59][pr59]) - Fix typos in payloads ([#57][pr57]): - `get_updates`: `offset` `i64` -> `i32` - `send_location`: make `live_period` optional @@ -16,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [pr56]: https://github.com/teloxide/teloxide-core/pull/56 [pr57]: https://github.com/teloxide/teloxide-core/pull/57 +[pr59]: https://github.com/teloxide/teloxide-core/pull/59 ## [0.1.1] - 2020-02-17 diff --git a/src/bot/api.rs b/src/bot/api.rs index 6ee44da9..c0801ad1 100644 --- a/src/bot/api.rs +++ b/src/bot/api.rs @@ -20,15 +20,11 @@ impl Requester for Bot { type SetWebhook = JsonRequest; - fn set_webhook(&self, url: U, allowed_updates: A) -> Self::SetWebhook + fn set_webhook(&self, url: U) -> Self::SetWebhook where U: Into, - A: IntoIterator, { - Self::SetWebhook::new( - self.clone(), - payloads::SetWebhook::new(url, allowed_updates), - ) + Self::SetWebhook::new(self.clone(), payloads::SetWebhook::new(url)) } type DeleteWebhook = JsonRequest; diff --git a/src/local_macros.rs b/src/local_macros.rs index bf743779..760bc558 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -393,7 +393,7 @@ macro_rules! impl_payload { } #[macro_use] -// This macro is auto generated by `cg` (8ee7ef2). +// This macro is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS MACRO**, // edit `cg` instead. macro_rules! requester_forward { @@ -403,8 +403,6 @@ macro_rules! requester_forward { requester_forward!(@method $rest $body $ty); )* }; - - (@method get_updates $body:ident $ty:ident) => { type GetUpdates = $ty![GetUpdates]; @@ -416,10 +414,9 @@ macro_rules! requester_forward { (@method set_webhook $body:ident $ty:ident) => { type SetWebhook = $ty![SetWebhook]; - fn set_webhook(&self, url: U, allowed_updates: A) -> Self::SetWebhook where U: Into, - A: IntoIterator { + fn set_webhook(&self, url: U) -> Self::SetWebhook where U: Into { let this = self; - $body!(set_webhook this (url: U, allowed_updates: A)) + $body!(set_webhook this (url: U)) } }; (@method delete_webhook $body:ident $ty:ident) => { diff --git a/src/payloads/add_sticker_to_set.rs b/src/payloads/add_sticker_to_set.rs index 2541b79d..4366de42 100644 --- a/src/payloads/add_sticker_to_set.rs +++ b/src/payloads/add_sticker_to_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/answer_callback_query.rs b/src/payloads/answer_callback_query.rs index f5c3a849..8c636ac7 100644 --- a/src/payloads/answer_callback_query.rs +++ b/src/payloads/answer_callback_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/answer_inline_query.rs b/src/payloads/answer_inline_query.rs index ffc4f8ee..b81e0892 100644 --- a/src/payloads/answer_inline_query.rs +++ b/src/payloads/answer_inline_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/answer_pre_checkout_query.rs b/src/payloads/answer_pre_checkout_query.rs index 356757c7..375180b0 100644 --- a/src/payloads/answer_pre_checkout_query.rs +++ b/src/payloads/answer_pre_checkout_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/answer_shipping_query.rs b/src/payloads/answer_shipping_query.rs index c9d2b511..46cd48a1 100644 --- a/src/payloads/answer_shipping_query.rs +++ b/src/payloads/answer_shipping_query.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/create_new_sticker_set.rs b/src/payloads/create_new_sticker_set.rs index 5c716a20..a763e61b 100644 --- a/src/payloads/create_new_sticker_set.rs +++ b/src/payloads/create_new_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_chat_photo.rs b/src/payloads/delete_chat_photo.rs index a6a5906e..db346e4e 100644 --- a/src/payloads/delete_chat_photo.rs +++ b/src/payloads/delete_chat_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_chat_sticker_set.rs b/src/payloads/delete_chat_sticker_set.rs index 5070cff5..c8b66e32 100644 --- a/src/payloads/delete_chat_sticker_set.rs +++ b/src/payloads/delete_chat_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_message.rs b/src/payloads/delete_message.rs index 3bf206f4..3e113023 100644 --- a/src/payloads/delete_message.rs +++ b/src/payloads/delete_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_sticker_from_set.rs b/src/payloads/delete_sticker_from_set.rs index 78d4f0c1..c3f30b59 100644 --- a/src/payloads/delete_sticker_from_set.rs +++ b/src/payloads/delete_sticker_from_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/delete_webhook.rs b/src/payloads/delete_webhook.rs index 6a2cf0d5..2822e14a 100644 --- a/src/payloads/delete_webhook.rs +++ b/src/payloads/delete_webhook.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_caption.rs b/src/payloads/edit_message_caption.rs index ddcdd6b9..8216f859 100644 --- a/src/payloads/edit_message_caption.rs +++ b/src/payloads/edit_message_caption.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_caption_inline.rs b/src/payloads/edit_message_caption_inline.rs index 819d6b83..661d7fd9 100644 --- a/src/payloads/edit_message_caption_inline.rs +++ b/src/payloads/edit_message_caption_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_live_location.rs b/src/payloads/edit_message_live_location.rs index 322e40e0..5e7aaba4 100644 --- a/src/payloads/edit_message_live_location.rs +++ b/src/payloads/edit_message_live_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_live_location_inline.rs b/src/payloads/edit_message_live_location_inline.rs index 1d650933..348fa138 100644 --- a/src/payloads/edit_message_live_location_inline.rs +++ b/src/payloads/edit_message_live_location_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_media.rs b/src/payloads/edit_message_media.rs index 958d147d..c0c3388c 100644 --- a/src/payloads/edit_message_media.rs +++ b/src/payloads/edit_message_media.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_media_inline.rs b/src/payloads/edit_message_media_inline.rs index 8462873d..47bd4600 100644 --- a/src/payloads/edit_message_media_inline.rs +++ b/src/payloads/edit_message_media_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_reply_markup.rs b/src/payloads/edit_message_reply_markup.rs index 70d17489..b580cfe2 100644 --- a/src/payloads/edit_message_reply_markup.rs +++ b/src/payloads/edit_message_reply_markup.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_reply_markup_inline.rs b/src/payloads/edit_message_reply_markup_inline.rs index 40af6ab8..0cfbf92e 100644 --- a/src/payloads/edit_message_reply_markup_inline.rs +++ b/src/payloads/edit_message_reply_markup_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_text.rs b/src/payloads/edit_message_text.rs index 5d8e72b5..a9d5dcce 100644 --- a/src/payloads/edit_message_text.rs +++ b/src/payloads/edit_message_text.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/edit_message_text_inline.rs b/src/payloads/edit_message_text_inline.rs index 90f08afc..9e51eca3 100644 --- a/src/payloads/edit_message_text_inline.rs +++ b/src/payloads/edit_message_text_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/export_chat_invite_link.rs b/src/payloads/export_chat_invite_link.rs index 1e8fc54e..0cfd1b10 100644 --- a/src/payloads/export_chat_invite_link.rs +++ b/src/payloads/export_chat_invite_link.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/forward_message.rs b/src/payloads/forward_message.rs index f1248b0f..fd33171d 100644 --- a/src/payloads/forward_message.rs +++ b/src/payloads/forward_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat.rs b/src/payloads/get_chat.rs index 2abd7787..403629f3 100644 --- a/src/payloads/get_chat.rs +++ b/src/payloads/get_chat.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat_administrators.rs b/src/payloads/get_chat_administrators.rs index b950cbeb..49c05bf6 100644 --- a/src/payloads/get_chat_administrators.rs +++ b/src/payloads/get_chat_administrators.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat_member.rs b/src/payloads/get_chat_member.rs index e78ac0f4..4f91f285 100644 --- a/src/payloads/get_chat_member.rs +++ b/src/payloads/get_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_chat_members_count.rs b/src/payloads/get_chat_members_count.rs index 4a363da0..237c4d33 100644 --- a/src/payloads/get_chat_members_count.rs +++ b/src/payloads/get_chat_members_count.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_file.rs b/src/payloads/get_file.rs index 921ed5a7..0535b768 100644 --- a/src/payloads/get_file.rs +++ b/src/payloads/get_file.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_game_high_scores.rs b/src/payloads/get_game_high_scores.rs index b61320a4..0aaa6e27 100644 --- a/src/payloads/get_game_high_scores.rs +++ b/src/payloads/get_game_high_scores.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_me.rs b/src/payloads/get_me.rs index d84ccbe5..66b6c48a 100644 --- a/src/payloads/get_me.rs +++ b/src/payloads/get_me.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_my_commands.rs b/src/payloads/get_my_commands.rs index 29193161..f5591f29 100644 --- a/src/payloads/get_my_commands.rs +++ b/src/payloads/get_my_commands.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_sticker_set.rs b/src/payloads/get_sticker_set.rs index a30bfd2b..e9e35f5a 100644 --- a/src/payloads/get_sticker_set.rs +++ b/src/payloads/get_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_updates.rs b/src/payloads/get_updates.rs index df63aa63..053bca02 100644 --- a/src/payloads/get_updates.rs +++ b/src/payloads/get_updates.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_user_profile_photos.rs b/src/payloads/get_user_profile_photos.rs index 2bc88808..5da97186 100644 --- a/src/payloads/get_user_profile_photos.rs +++ b/src/payloads/get_user_profile_photos.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/get_webhook_info.rs b/src/payloads/get_webhook_info.rs index bd3414e5..e3e9d050 100644 --- a/src/payloads/get_webhook_info.rs +++ b/src/payloads/get_webhook_info.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/kick_chat_member.rs b/src/payloads/kick_chat_member.rs index d456192d..e4eb968b 100644 --- a/src/payloads/kick_chat_member.rs +++ b/src/payloads/kick_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/leave_chat.rs b/src/payloads/leave_chat.rs index c96dc435..3c237c7e 100644 --- a/src/payloads/leave_chat.rs +++ b/src/payloads/leave_chat.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/pin_chat_message.rs b/src/payloads/pin_chat_message.rs index feb81b30..6ad729cc 100644 --- a/src/payloads/pin_chat_message.rs +++ b/src/payloads/pin_chat_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/promote_chat_member.rs b/src/payloads/promote_chat_member.rs index b16b2a47..7af9050b 100644 --- a/src/payloads/promote_chat_member.rs +++ b/src/payloads/promote_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/restrict_chat_member.rs b/src/payloads/restrict_chat_member.rs index 10ba222c..648b92fc 100644 --- a/src/payloads/restrict_chat_member.rs +++ b/src/payloads/restrict_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_animation.rs b/src/payloads/send_animation.rs index cd6d010e..1cd351a4 100644 --- a/src/payloads/send_animation.rs +++ b/src/payloads/send_animation.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_audio.rs b/src/payloads/send_audio.rs index 95bc7f21..9caf22ad 100644 --- a/src/payloads/send_audio.rs +++ b/src/payloads/send_audio.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_chat_action.rs b/src/payloads/send_chat_action.rs index 8b614607..1d5cd378 100644 --- a/src/payloads/send_chat_action.rs +++ b/src/payloads/send_chat_action.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_contact.rs b/src/payloads/send_contact.rs index f6b464fc..ed08f42f 100644 --- a/src/payloads/send_contact.rs +++ b/src/payloads/send_contact.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_dice.rs b/src/payloads/send_dice.rs index 7f885f91..0aa5e59f 100644 --- a/src/payloads/send_dice.rs +++ b/src/payloads/send_dice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_document.rs b/src/payloads/send_document.rs index 828b7314..fca931ef 100644 --- a/src/payloads/send_document.rs +++ b/src/payloads/send_document.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_game.rs b/src/payloads/send_game.rs index e62e557f..6b657a55 100644 --- a/src/payloads/send_game.rs +++ b/src/payloads/send_game.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_invoice.rs b/src/payloads/send_invoice.rs index db8740af..ede44d1a 100644 --- a/src/payloads/send_invoice.rs +++ b/src/payloads/send_invoice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_location.rs b/src/payloads/send_location.rs index d9cbbd6e..28c8c329 100644 --- a/src/payloads/send_location.rs +++ b/src/payloads/send_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_media_group.rs b/src/payloads/send_media_group.rs index 9593a330..fba6ac81 100644 --- a/src/payloads/send_media_group.rs +++ b/src/payloads/send_media_group.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_message.rs b/src/payloads/send_message.rs index 609bfb28..07815164 100644 --- a/src/payloads/send_message.rs +++ b/src/payloads/send_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_photo.rs b/src/payloads/send_photo.rs index 6d9b8fe1..d35cec82 100644 --- a/src/payloads/send_photo.rs +++ b/src/payloads/send_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_poll.rs b/src/payloads/send_poll.rs index cbe48b33..a141e7e7 100644 --- a/src/payloads/send_poll.rs +++ b/src/payloads/send_poll.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_sticker.rs b/src/payloads/send_sticker.rs index 3992ab53..1434cd55 100644 --- a/src/payloads/send_sticker.rs +++ b/src/payloads/send_sticker.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_venue.rs b/src/payloads/send_venue.rs index 361db666..4805199b 100644 --- a/src/payloads/send_venue.rs +++ b/src/payloads/send_venue.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_video.rs b/src/payloads/send_video.rs index 49b585d5..3321f5ef 100644 --- a/src/payloads/send_video.rs +++ b/src/payloads/send_video.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_video_note.rs b/src/payloads/send_video_note.rs index 49f6ac29..3001ad53 100644 --- a/src/payloads/send_video_note.rs +++ b/src/payloads/send_video_note.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/send_voice.rs b/src/payloads/send_voice.rs index 9b7a7698..8f7edb15 100644 --- a/src/payloads/send_voice.rs +++ b/src/payloads/send_voice.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_administrator_custom_title.rs b/src/payloads/set_chat_administrator_custom_title.rs index bf9ea9f4..aeab4408 100644 --- a/src/payloads/set_chat_administrator_custom_title.rs +++ b/src/payloads/set_chat_administrator_custom_title.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_description.rs b/src/payloads/set_chat_description.rs index f5239b2e..e964a799 100644 --- a/src/payloads/set_chat_description.rs +++ b/src/payloads/set_chat_description.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_permissions.rs b/src/payloads/set_chat_permissions.rs index c41b0a77..334c53cd 100644 --- a/src/payloads/set_chat_permissions.rs +++ b/src/payloads/set_chat_permissions.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_photo.rs b/src/payloads/set_chat_photo.rs index 4d4d4d3b..46723c03 100644 --- a/src/payloads/set_chat_photo.rs +++ b/src/payloads/set_chat_photo.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_sticker_set.rs b/src/payloads/set_chat_sticker_set.rs index 4ce833b9..3a6d3fe6 100644 --- a/src/payloads/set_chat_sticker_set.rs +++ b/src/payloads/set_chat_sticker_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_chat_title.rs b/src/payloads/set_chat_title.rs index bdd605b6..9df76f55 100644 --- a/src/payloads/set_chat_title.rs +++ b/src/payloads/set_chat_title.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_game_score.rs b/src/payloads/set_game_score.rs index a0cda56f..54ead85f 100644 --- a/src/payloads/set_game_score.rs +++ b/src/payloads/set_game_score.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_game_score_inline.rs b/src/payloads/set_game_score_inline.rs index c1f699c3..0e7aeb55 100644 --- a/src/payloads/set_game_score_inline.rs +++ b/src/payloads/set_game_score_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_my_commands.rs b/src/payloads/set_my_commands.rs index 2919a13b..951cf160 100644 --- a/src/payloads/set_my_commands.rs +++ b/src/payloads/set_my_commands.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_passport_data_errors.rs b/src/payloads/set_passport_data_errors.rs index 05321a36..ecfb1cbc 100644 --- a/src/payloads/set_passport_data_errors.rs +++ b/src/payloads/set_passport_data_errors.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_sticker_position_in_set.rs b/src/payloads/set_sticker_position_in_set.rs index 45804e7e..b264b2bc 100644 --- a/src/payloads/set_sticker_position_in_set.rs +++ b/src/payloads/set_sticker_position_in_set.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_sticker_set_thumb.rs b/src/payloads/set_sticker_set_thumb.rs index a515e230..c4a2cb11 100644 --- a/src/payloads/set_sticker_set_thumb.rs +++ b/src/payloads/set_sticker_set_thumb.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/set_webhook.rs b/src/payloads/set_webhook.rs index f584b6de..401d3f4b 100644 --- a/src/payloads/set_webhook.rs +++ b/src/payloads/set_webhook.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; @@ -17,12 +17,6 @@ impl_payload! { required { /// HTTPS url to send updates to. Use an empty string to remove webhook integration pub url: String [into], - /// A JSON-serialized list of the update types you want your bot to receive. For example, specify [“message”, “edited_channel_post”, “callback_query”] to only receive updates of these types. See [`Update`] for a complete list of available update types. Specify an empty list to receive all updates regardless of type (default). If not specified, the previous setting will be used. - /// - /// Please note that this parameter doesn't affect updates created before the call to the setWebhook, so unwanted updates may be received for a short period of time. - /// - /// [`Update`]: crate::types::Update - pub allowed_updates: Vec [collect], } optional { /// Upload your public key certificate so that the root certificate in use can be checked. See our [self-signed guide] for details. @@ -31,6 +25,12 @@ impl_payload! { pub certificate: InputFile, /// Maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to 40. Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput. pub max_connections: u8, + /// A JSON-serialized list of the update types you want your bot to receive. For example, specify [“message”, “edited_channel_post”, “callback_query”] to only receive updates of these types. See [`Update`] for a complete list of available update types. Specify an empty list to receive all updates regardless of type (default). If not specified, the previous setting will be used. + /// + /// Please note that this parameter doesn't affect updates created before the call to the setWebhook, so unwanted updates may be received for a short period of time. + /// + /// [`Update`]: crate::types::Update + pub allowed_updates: Vec [collect], } } } diff --git a/src/payloads/stop_message_live_location.rs b/src/payloads/stop_message_live_location.rs index ae251505..cd7a2dc2 100644 --- a/src/payloads/stop_message_live_location.rs +++ b/src/payloads/stop_message_live_location.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/stop_message_live_location_inline.rs b/src/payloads/stop_message_live_location_inline.rs index 09519515..802d51cf 100644 --- a/src/payloads/stop_message_live_location_inline.rs +++ b/src/payloads/stop_message_live_location_inline.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/stop_poll.rs b/src/payloads/stop_poll.rs index 708e920f..3446b9c7 100644 --- a/src/payloads/stop_poll.rs +++ b/src/payloads/stop_poll.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/unban_chat_member.rs b/src/payloads/unban_chat_member.rs index 4b7ae1ba..54768eac 100644 --- a/src/payloads/unban_chat_member.rs +++ b/src/payloads/unban_chat_member.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/unpin_chat_message.rs b/src/payloads/unpin_chat_message.rs index 04471d8c..9815c7e5 100644 --- a/src/payloads/unpin_chat_message.rs +++ b/src/payloads/unpin_chat_message.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/payloads/upload_sticker_file.rs b/src/payloads/upload_sticker_file.rs index e8777d76..74c9c346 100644 --- a/src/payloads/upload_sticker_file.rs +++ b/src/payloads/upload_sticker_file.rs @@ -1,4 +1,4 @@ -// This file is auto generated by `cg` (8ee7ef2). +// This file is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS FILE**, // edit `cg` instead. use serde::Serialize; diff --git a/src/requests/requester.rs b/src/requests/requester.rs index 69a79199..bd35b7d4 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -53,7 +53,7 @@ pub trait Requester { /// Error type returned by all requests. type Err: std::error::Error + Send; - // This block is auto generated by `cg` (8ee7ef2). + // This block is auto generated by `cg` (24572cd + local changes). // **DO NOT EDIT THIS BLOCK**, // edit `cg` instead. @@ -65,10 +65,9 @@ pub trait Requester { type SetWebhook: Request; /// For Telegram documentation see [`SetWebhook`]. - fn set_webhook(&self, url: U, allowed_updates: A) -> Self::SetWebhook + fn set_webhook(&self, url: U) -> Self::SetWebhook where - U: Into, - A: IntoIterator; + U: Into; type DeleteWebhook: Request; From b3d8d70a7c7caacfcc238be21b26a7e392ebbf2d Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 6 Mar 2021 10:58:18 +0300 Subject: [PATCH 249/755] remove unused use --- src/requests/requester.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/requests/requester.rs b/src/requests/requester.rs index bd35b7d4..6df4d047 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -5,9 +5,8 @@ use crate::{ payloads::{GetMe, SendMessage, *}, requests::Request, types::{ - AllowedUpdate, BotCommand, ChatAction, ChatId, ChatPermissions, InlineQueryResult, - InputFile, InputMedia, InputSticker, LabeledPrice, PassportElementError, PollType, - TargetMessage, + BotCommand, ChatAction, ChatId, ChatPermissions, InlineQueryResult, InputFile, InputMedia, + InputSticker, LabeledPrice, PassportElementError, PollType, TargetMessage, }, }; From 201a60b38a7a5ff3383bb00ff17cd1b8056ef60e Mon Sep 17 00:00:00 2001 From: Waffle Date: Sat, 6 Mar 2021 10:58:54 +0300 Subject: [PATCH 250/755] Temporary switch clippy CI to beta b/c of ICE --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 939b0930..4ab0617b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,7 +56,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly + toolchain: beta #nightly override: true components: clippy @@ -64,7 +64,8 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: --all-targets --all-features -- -D warnings + args: --all-targets --features full -- -D warnings + #args: --all-targets --all-features -- -D warnings style: runs-on: ubuntu-latest From 61eed38c32476ae09a0b9981f95e086c9d324692 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 13 Mar 2021 17:46:16 +0600 Subject: [PATCH 251/755] Fix SemiparsedVec --- Cargo.toml | 1 + src/types.rs | 4 ++-- .../{non_strict_vec.rs => semiparsed_vec.rs} | 11 +++++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) rename src/types/non_telegram_types/{non_strict_vec.rs => semiparsed_vec.rs} (74%) diff --git a/Cargo.toml b/Cargo.toml index 640df040..06a14ebd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ tokio-util = "0.6.0" pin-project = "1.0.3" bytes = "1.0.0" reqwest = { version = "0.11.0", features = ["json", "stream", "multipart"] } +log = "0.4" serde = { version = "1.0.114", features = ["derive"] } serde_json = "1.0.55" diff --git a/src/types.rs b/src/types.rs index 87e45a62..5231bf96 100644 --- a/src/types.rs +++ b/src/types.rs @@ -188,10 +188,10 @@ mod passport_data; mod passport_element_error; mod passport_file; -pub use non_telegram_types::{country_code::*, currency::*, non_strict_vec::*}; +pub use non_telegram_types::{country_code::*, currency::*, semiparsed_vec::*}; mod non_telegram_types { pub(super) mod country_code; pub(super) mod currency; pub(crate) mod mime; - pub(super) mod non_strict_vec; + pub(super) mod semiparsed_vec; } diff --git a/src/types/non_telegram_types/non_strict_vec.rs b/src/types/non_telegram_types/semiparsed_vec.rs similarity index 74% rename from src/types/non_telegram_types/non_strict_vec.rs rename to src/types/non_telegram_types/semiparsed_vec.rs index 6d2e5916..e40e4dfc 100644 --- a/src/types/non_telegram_types/non_strict_vec.rs +++ b/src/types/non_telegram_types/semiparsed_vec.rs @@ -4,7 +4,7 @@ use serde_json::{from_value, Value}; /// A vector of possibly unparsed JSON objects. /// /// Similar to `Vec` but if it fails to deserialize element, it just saves -/// `Err((serde_json::Value, serde_json::Error))`. +/// `Err((serde_json::Value, serde_json::Error))` and logs an error. #[derive(Debug, serde::Deserialize)] #[serde(from = "Vec")] #[serde(bound = "T: DeserializeOwned")] @@ -14,7 +14,14 @@ impl From> for SemiparsedVec { fn from(vec: Vec) -> Self { Self( vec.into_iter() - .map(|val| from_value(val.clone()).map_err(|e| (val, e))) + .map(|val| { + from_value(val.clone()).map_err(|e| { + log::error!("Cannot parse a JSON object.\nError: {:?}\nValue: {}\n\ + This is a bug in teloxide-core, please open an issue here: \ + https://github.com/teloxide/teloxide-core/issues.", e, val); + (val, e) + }) + }) .collect(), ) } From 32b446d26c7157276039ef814cfdecab6a3ce306 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 13 Mar 2021 21:05:45 +0600 Subject: [PATCH 252/755] Move log::error! on update parsing failure to Update::try_parse --- .../non_telegram_types/semiparsed_vec.rs | 15 ++----------- src/types/update.rs | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/types/non_telegram_types/semiparsed_vec.rs b/src/types/non_telegram_types/semiparsed_vec.rs index e40e4dfc..4edce6c3 100644 --- a/src/types/non_telegram_types/semiparsed_vec.rs +++ b/src/types/non_telegram_types/semiparsed_vec.rs @@ -4,7 +4,7 @@ use serde_json::{from_value, Value}; /// A vector of possibly unparsed JSON objects. /// /// Similar to `Vec` but if it fails to deserialize element, it just saves -/// `Err((serde_json::Value, serde_json::Error))` and logs an error. +/// `Err((serde_json::Value, serde_json::Error))`. #[derive(Debug, serde::Deserialize)] #[serde(from = "Vec")] #[serde(bound = "T: DeserializeOwned")] @@ -12,18 +12,7 @@ pub struct SemiparsedVec(pub Vec From> for SemiparsedVec { fn from(vec: Vec) -> Self { - Self( - vec.into_iter() - .map(|val| { - from_value(val.clone()).map_err(|e| { - log::error!("Cannot parse a JSON object.\nError: {:?}\nValue: {}\n\ - This is a bug in teloxide-core, please open an issue here: \ - https://github.com/teloxide/teloxide-core/issues.", e, val); - (val, e) - }) - }) - .collect(), - ) + Self(vec.into_iter().map(|val| (val, e)).collect()) } } diff --git a/src/types/update.rs b/src/types/update.rs index 4ebc5710..c1d7e10f 100644 --- a/src/types/update.rs +++ b/src/types/update.rs @@ -6,6 +6,7 @@ use crate::types::{ CallbackQuery, Chat, ChosenInlineResult, InlineQuery, Message, Poll, PollAnswer, PreCheckoutQuery, ShippingQuery, User, }; +use serde_json::Value; /// This [object] represents an incoming update. /// @@ -28,6 +29,27 @@ pub struct Update { pub kind: UpdateKind, } +impl Update { + /// Tries to parse `value` into `Update`, logging an error on failure. + /// + /// It is used to implement update listeners. + pub fn try_parse(value: &Value) -> Result { + match serde_json::from_str(&value.to_string()) { + Ok(update) => Ok(update), + Err(error) => { + log::error!( + "Cannot parse an update.\nError: {:?}\nValue: {}\n\ + This is a bug in teloxide-core, please open an issue here: \ + https://github.com/teloxide/teloxide-core/issues.", + error, + value + ); + Err(error) + } + } + } +} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum UpdateKind { From cd9a98163c976b8433a38f8cc898590bfcf5b068 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 13 Mar 2021 21:11:54 +0600 Subject: [PATCH 253/755] Fix SemiparsedVec::from --- src/types/non_telegram_types/semiparsed_vec.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/types/non_telegram_types/semiparsed_vec.rs b/src/types/non_telegram_types/semiparsed_vec.rs index 4edce6c3..6d2e5916 100644 --- a/src/types/non_telegram_types/semiparsed_vec.rs +++ b/src/types/non_telegram_types/semiparsed_vec.rs @@ -12,7 +12,11 @@ pub struct SemiparsedVec(pub Vec From> for SemiparsedVec { fn from(vec: Vec) -> Self { - Self(vec.into_iter().map(|val| (val, e)).collect()) + Self( + vec.into_iter() + .map(|val| from_value(val.clone()).map_err(|e| (val, e))) + .collect(), + ) } } From e400fb16e7add288f7958cf0cc873e477fa1d875 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 13 Mar 2021 21:38:05 +0600 Subject: [PATCH 254/755] Use serde_json::from_value in Update::try_parse --- src/types/update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/update.rs b/src/types/update.rs index c1d7e10f..b4f8aecd 100644 --- a/src/types/update.rs +++ b/src/types/update.rs @@ -34,7 +34,7 @@ impl Update { /// /// It is used to implement update listeners. pub fn try_parse(value: &Value) -> Result { - match serde_json::from_str(&value.to_string()) { + match serde_json::from_value(value.clone()) { Ok(update) => Ok(update), Err(error) => { log::error!( From 20c67a18c5ad6ac01fa3aff537e0713647f1d20d Mon Sep 17 00:00:00 2001 From: Waffle Date: Sun, 14 Mar 2021 17:10:06 +0300 Subject: [PATCH 255/755] Update types (tba API 5.0) See more: https://core.telegram.org/bots/api#november-4-2020 --- src/types.rs | 6 ++ src/types/audio.rs | 4 + src/types/chat.rs | 52 +++++++++--- src/types/chat_location.rs | 13 +++ src/types/chat_member.rs | 10 ++- src/types/dice_emoji.rs | 8 ++ src/types/inline_query_result.rs | 3 + src/types/inline_query_result_audio.rs | 15 +++- src/types/inline_query_result_cached_audio.rs | 15 +++- .../inline_query_result_cached_document.rs | 15 +++- src/types/inline_query_result_cached_gif.rs | 15 +++- .../inline_query_result_cached_mpeg4_gif.rs | 15 +++- src/types/inline_query_result_cached_photo.rs | 15 +++- src/types/inline_query_result_cached_video.rs | 15 +++- src/types/inline_query_result_cached_voice.rs | 15 +++- src/types/inline_query_result_document.rs | 14 +++- src/types/inline_query_result_gif.rs | 15 +++- src/types/inline_query_result_location.rs | 38 ++++++++- src/types/inline_query_result_mpeg4_gif.rs | 15 +++- src/types/inline_query_result_photo.rs | 15 +++- src/types/inline_query_result_venue.rs | 26 ++++++ src/types/inline_query_result_video.rs | 15 +++- src/types/inline_query_result_voice.rs | 15 +++- src/types/input_media.rs | 71 ++++++++++++++++- src/types/input_message_content.rs | 46 ++++++++++- src/types/location.rs | 15 ++++ src/types/message.rs | 79 ++++++++++++++----- src/types/message_id.rs | 5 ++ src/types/proximity_alert_triggered.rs | 17 ++++ src/types/update.rs | 4 + src/types/venue.rs | 8 ++ src/types/video.rs | 3 + src/types/webhook_info.rs | 3 + 33 files changed, 567 insertions(+), 53 deletions(-) create mode 100644 src/types/chat_location.rs create mode 100644 src/types/message_id.rs create mode 100644 src/types/proximity_alert_triggered.rs diff --git a/src/types.rs b/src/types.rs index 5231bf96..5e3936d8 100644 --- a/src/types.rs +++ b/src/types.rs @@ -9,6 +9,7 @@ pub use callback_query::*; pub use chat::*; pub use chat_action::*; pub use chat_id::*; +pub use chat_location::*; pub use chat_member::*; pub use chat_permissions::*; pub use chat_photo::*; @@ -61,6 +62,7 @@ pub use mask_position::*; pub use me::*; pub use message::*; pub use message_entity::*; +pub use message_id::*; pub use order_info::*; pub use parse_mode::*; pub use passport_data::*; @@ -71,6 +73,7 @@ pub use poll::*; pub use poll_answer::*; pub use poll_type::*; pub use pre_checkout_query::*; +pub use proximity_alert_triggered::*; pub use reply_keyboard_markup::*; pub use reply_keyboard_remove::*; pub use reply_markup::*; @@ -102,6 +105,7 @@ mod callback_query; mod chat; mod chat_action; mod chat_id; +mod chat_location; mod chat_member; mod chat_permissions; mod chat_photo; @@ -130,6 +134,7 @@ mod mask_position; mod me; mod message; mod message_entity; +mod message_id; mod order_info; mod parse_mode; mod photo_size; @@ -137,6 +142,7 @@ mod poll; mod poll_answer; mod poll_type; mod pre_checkout_query; +mod proximity_alert_triggered; mod reply_keyboard_markup; mod reply_keyboard_remove; mod reply_markup; diff --git a/src/types/audio.rs b/src/types/audio.rs index 1bf3174f..ac7a1f3b 100644 --- a/src/types/audio.rs +++ b/src/types/audio.rs @@ -27,6 +27,9 @@ pub struct Audio { /// A title of the audio as defined by sender or by audio tags. pub title: Option, + /// Original filename as defined by sender + pub file_name: Option, + /// A MIME type of the file as defined by a sender. #[serde(with = "crate::types::non_telegram_types::mime::opt_deser")] pub mime_type: Option, @@ -75,6 +78,7 @@ mod tests { height: 320, file_size: Some(3452), }), + file_name: None, }; let actual = serde_json::from_str::