Merge branch 'dev' into conversion_traits_for_parse_mode

This commit is contained in:
Waffle Lapkin 2020-01-01 03:28:08 +03:00 committed by GitHub
commit 585c161511
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 306 additions and 50 deletions

View file

@ -18,7 +18,8 @@ use crate::{
RestrictChatMember, SendAnimation, SendAudio, SendChatAction,
SendContact, SendDocument, SendGame, SendInvoice, SendLocation,
SendMediaGroup, SendMessage, SendPhoto, SendPoll, SendSticker,
SendVenue, SendVideo, SendVideoNote, SendVoice, SetChatDescription,
SendVenue, SendVideo, SendVideoNote, SendVoice,
SetChatAdministratorCustomTitle, SetChatDescription,
SetChatPermission, SetChatPhoto, SetChatStickerSet, SetChatTitle,
SetGameScore, SetGameScoreInline, SetStickerPositionInSet,
SetWebhook, StopMessageLiveLocation, StopMessageLiveLocationInline,
@ -1150,4 +1151,28 @@ impl Bot {
GetGameHighScore::new(chat_id, message_id, user_id),
)
}
/// For tg-method documentation see [`SetChatAdministratorCustomTitle`]
///
/// [`SetChatAdministratorCustomTitle`]:
/// crate::requests::payloads::SetChatAdministratorCustomTitle
pub fn set_chat_administrator_custom_title<C, CT>(
&self,
chat_id: C,
user_id: i32,
custom_title: CT,
) -> json::Request<SetChatAdministratorCustomTitle>
where
C: Into<ChatId>,
CT: Into<String>,
{
json::Request::new(
self,
SetChatAdministratorCustomTitle::new(
chat_id,
user_id,
custom_title,
),
)
}
}

View file

@ -28,7 +28,7 @@ type FiltersWithHandlers<'a, T, E> = Vec<FilterWithHandler<'a, T, E>>;
/// acts:
///
/// <div align="center">
/// <img src="https://github.com/teloxide/teloxide/blob/dev/media/FILTER_DP_FLOWCHART.png" width="700" />
/// <img src="https://raw.githubusercontent.com/teloxide/teloxide/dev/media/FILTER_DP_FLOWCHART.png" width="700" />
/// </div>
///
/// ## Examples
@ -69,8 +69,10 @@ type FiltersWithHandlers<'a, T, E> = Vec<FilterWithHandler<'a, T, E>>;
/// ```
///
/// [`std::fmt::Debug`]: std::fmt::Debug
/// [updater]: crate::dispatching::updater
/// [`.dispatch(updater)`]: FilterDispatcher::dispatch
/// [`ErrorHandler`]: crate::dispatching::error_handlers::ErrorHandler
/// [`Updater`]: crate::dispatching::updaters::Updater
/// [`Handler`]: crate::dispatching::Handler
pub struct FilterDispatcher<'a, E, Eh> {
message_handlers: FiltersWithHandlers<'a, Message, E>,
edited_message_handlers: FiltersWithHandlers<'a, Message, E>,

View file

@ -0,0 +1 @@
pub mod filter;

View file

@ -1,13 +1,13 @@
//! Update dispatching.
mod dispatchers;
pub mod error_handlers;
mod filter_dp;
pub mod filters;
mod handler;
pub mod updaters;
pub use dispatchers::filter::FilterDispatcher;
pub use error_handlers::ErrorHandler;
pub use filter_dp::FilterDispatcher;
pub use filters::Filter;
pub use handler::Handler;
pub use updaters::Updater;

View file

@ -123,7 +123,9 @@ impl IntoFormValue for str {
impl IntoFormValue for ParseMode {
fn into_form_value(&self) -> Option<FormValue> {
let string = match self {
ParseMode::MarkdownV2 => String::from("MarkdownV2"),
ParseMode::HTML => String::from("HTML"),
#[allow(deprecated)]
ParseMode::Markdown => String::from("Markdown"),
};
Some(FormValue::Str(string))

View file

@ -115,6 +115,7 @@ pub mod payloads {
mod set_game_score;
mod get_game_high_scores;
mod get_game_high_scores_inline;
mod set_chat_administrator_custom_title;
pub use {
get_updates::{GetUpdates, AllowedUpdate},
@ -189,5 +190,6 @@ pub mod payloads {
set_game_score::SetGameScore,
get_game_high_scores_inline::GetGameHighScoreInline,
get_game_high_scores::GetGameHighScore,
set_chat_administrator_custom_title::SetChatAdministratorCustomTitle,
};
}

View file

@ -0,0 +1,76 @@
use serde::{Deserialize, Serialize};
use crate::{
requests::{dynamic, json, Method},
types::{True, ChatId},
};
/// Use this method to set a custom title for an administrator in a supergroup
/// promoted by the bot.
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)]
pub struct SetChatAdministratorCustomTitle {
/// Unique identifier for the target chat or username of the target
/// supergroup (in the format `@supergroupusername`)
pub chat_id: ChatId,
/// Unique identifier of the target user
pub user_id: i32,
/// New custom title for the administrator; 0-16 characters, emoji are not
/// allowed
pub custom_title: String,
}
impl Method for SetChatAdministratorCustomTitle {
type Output = True;
const NAME: &'static str = "setChatAdministratorCustomTitle";
}
impl json::Payload for SetChatAdministratorCustomTitle {}
impl dynamic::Payload for SetChatAdministratorCustomTitle {
fn kind(&self) -> dynamic::Kind {
dynamic::Kind::Json(serde_json::to_string(self).unwrap())
}
}
impl SetChatAdministratorCustomTitle {
pub fn new<C, CT>(chat_id: C, user_id: i32, custom_title: CT) -> Self
where
C: Into<ChatId>,
CT: Into<String>,
{
let chat_id = chat_id.into();
let custom_title = custom_title.into();
Self {
chat_id,
user_id,
custom_title,
}
}
}
impl json::Request<'_, SetChatAdministratorCustomTitle> {
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.payload.chat_id = val.into();
self
}
pub fn user_id(mut self, val: i32) -> Self {
self.payload.user_id = val;
self
}
pub fn custom_title<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.payload.custom_title = val.into();
self
}
}

View file

@ -12,6 +12,11 @@ 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,
@ -42,11 +47,13 @@ mod tests {
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
@ -56,11 +63,13 @@ mod tests {
"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),

View file

@ -12,6 +12,11 @@ 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,
@ -39,6 +44,7 @@ mod tests {
fn deserialize() {
let json = r#"{
"file_id":"id",
"file_unique_id":"",
"duration":60,
"performer":"Performer",
"title":"Title",
@ -46,6 +52,7 @@ mod tests {
"file_size":123456,
"thumb":{
"file_id":"id",
"file_unique_id":"",
"width":320,
"height":320,
"file_size":3452
@ -53,6 +60,7 @@ mod tests {
}"#;
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()),
@ -60,6 +68,7 @@ mod tests {
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),

View file

@ -117,6 +117,12 @@ pub enum NonPrivateChatKind {
///
/// [`Bot::get_chat`]: crate::Bot::get_chat
permissions: Option<ChatPermissions>,
/// The minimum allowed delay between consecutive messages sent by each
/// unpriviledged user. Returned only in [`Bot::get_chat`].
///
/// [`Bot::get_chat`]: crate::Bot::get_chat
slow_mode_delay: Option<i32>,
},
}

View file

@ -2,6 +2,7 @@ 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).
@ -13,6 +14,9 @@ pub struct ChatMember {
/// The member's status in the chat.
pub status: ChatMemberStatus,
/// Owner and administrators only. Custom title for this user
pub custom_title: Option<String>,
/// Restricted and kicked only. Date when restrictions will be lifted for
/// this user, unix time.
pub until_date: Option<i32>,
@ -123,6 +127,7 @@ mod tests {
language_code: None,
},
status: ChatMemberStatus::Creator,
custom_title: None,
until_date: Some(123_456),
can_be_edited: Some(true),
can_change_info: Some(true),

View file

@ -10,8 +10,18 @@ pub struct ChatPhoto {
/// not changed.
pub small_file_id: String,
/// Unique file identifier of small (160x160) chat photo, which is supposed
/// to be the same over time and for different bots. Can't be used to
/// download or reuse the file.
pub small_file_unique_id: String,
/// A file identifier of big (640x640) chat photo. This file_id can be used
/// only for photo download and only for as long as the photo is not
/// changed.
pub big_file_id: String,
/// Unique file identifier of big (640x640) chat photo, which is supposed
/// to be the same over time and for different bots. Can't be used to
/// download or reuse the file.
pub big_file_unique_id: String,
}

View file

@ -16,6 +16,11 @@ pub struct Document {
/// 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 document thumbnail as defined by a sender.
pub thumb: Option<PhotoSize>,

View file

@ -22,6 +22,7 @@ pub struct EncryptedPassportElement {
#[serde_with_macros::skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
#[allow(clippy::large_enum_variant)]
pub enum EncryptedPassportElementKind {
PersonalDetails {
/// Base64-encoded encrypted Telegram Passport element data provided

View file

@ -13,6 +13,11 @@ 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,

View file

@ -91,12 +91,12 @@ mod tests {
reply_markup: Some(InlineKeyboardMarkup::new()),
input_message_content: Some(InputMessageContent::Text {
message_text: String::from("message_text"),
parse_mode: Some(ParseMode::Markdown),
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":"Markdown","disable_web_page_preview":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);

View file

@ -827,11 +827,13 @@ mod tests {
"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
}
}"#;
@ -867,11 +869,13 @@ mod tests {
"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
}
}"#;
@ -933,11 +937,13 @@ mod tests {
"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
}
}"#;
@ -968,18 +974,21 @@ mod tests {
"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

View file

@ -35,6 +35,8 @@ pub enum MessageEntityKind {
Pre,
TextLink { url: String },
TextMention { user: User },
Underline,
Strikethrough,
}
#[test]

View file

@ -1,78 +1,132 @@
use serde::export::TryFrom;
// 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::{str::FromStr, convert::TryFrom}
use serde::{Deserialize, Serialize};
use std::str::FromStr;
/// ## Formatting options
/// The Bot API supports basic formatting for messages.
/// You can use **bold** and *italic* text, as well as [inline links](https://example.com)
/// and `pre-formatted code` in your bots' messages. Telegram clients will
/// render them accordingly. You can use either markdown-style or HTML-style
/// 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).
/// 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=<user_id>` can be used to mention a user by their id
/// Links `tg://user?id=<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
/// - 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.
/// - The mentions are only guaranteed to work if: **A**. the user is a member
/// in the group where he was mentioned or **B**. the user has contacted the
/// bot in the past or has sent a callback query to the bot via inline button
/// and has not restricted linking to their account in `Settings > Privacy &
/// Security > Forwarded Messages`.
/// - 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.
///
/// ## Markdown style
/// To use this mode, pass [Markdown] in the `parse_mode` field when using
/// [SendMessage] (or other methods).
/// ## MarkdownV2 style
///
/// To use this mode, pass [`MarkdownV2`] in the `parse_mode` field.
/// Use the following syntax in your message:
///
/// <pre>
/// *bold text*
/// _italic text_
/// ````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)
/// &#96;inline fixed-width code&#96;
/// &#96;&#96;&#96;block_language
/// `inline fixed-width code`
/// ```
/// pre-formatted fixed-width code block
/// &#96;&#96;&#96;
/// </pre>
/// ```
/// ```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 when using
/// [SendMessage] (or other methods).
///
/// To use this mode, pass [`HTML`] in the `parse_mode` field.
/// The following tags are currently supported:
///
/// <pre>
/// &lt;b&gt;bold&lt;/b&gt;, &lt;strong&gt;bold&lt;/strong&gt;
/// &lt;i&gt;italic&lt;/i&gt;, &lt;em&gt;italic&lt;/em&gt;
/// &lt;a href="http://www.example.com/"&gt;inline URL&lt;/a&gt;
/// &lt;a href="tg://user?id=123456789"&gt;inline mention of a user&lt;/a&gt;
/// &lt;code&gt;inline fixed-width code&lt;/code&gt;
/// &lt;pre&gt;pre-formatted fixed-width code block&lt;/pre&gt;
/// </pre>
/// ````text
/// <b>bold</b>, <strong>bold</strong>
/// <i>italic</i>, <em>italic</em>
/// <u>underline</u>, <ins>underline</ins>
/// <s>strikethrough</s>, <strike>strikethrough</strike>,
/// <del>strikethrough</del> <b>bold <i>italic bold <s>italic bold
/// strikethrough</s> <u>underline italic bold</u></i> bold</b> <a href="http:// www.example.com/">inline URL</a>
/// <a href="tg:// user?id=123456789">inline mention of a user</a>
/// <code>inline fixed-width code</code>
/// <pre>pre-formatted fixed-width code block</pre>
/// <pre><code class="language-rust">pre-formatted fixed-width code block
/// written in the Rust programming language</code></pre> ````
///
/// Please note:
///
/// - Only the tags mentioned above are currently supported.
/// - Tags must not be nested.
/// - All `<`, `>` and `&` symbols that are not a part of a tag or an HTML
/// entity must be replaced with the corresponding HTML entities (`<` with
/// `&lt;`, `>` with `&gt;` and `&` with `&amp;`).
/// - All numerical HTML entities are supported.
/// - The API currently supports only the following named HTML entities: `&lt;`,
/// `&gt;`, `&amp;` and `&quot;`.
/// - Use nested `pre` and `code` tags, to define programming language for `pre`
/// entity.
/// - Programming language can't be specified for standalone `code` tags.
///
/// [Markdown]: crate::types::ParseMode::Markdown
/// [HTML]: crate::types::ParseMode::HTML
/// [SendMessage]: crate::requests::payloads::SendMessage
/// ## 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(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum ParseMode {
MarkdownV2,
HTML,
#[deprecated = "This is a legacy mode, retained for backward \
compatibility. Use `MarkdownV2` instead."]
Markdown,
}
@ -117,6 +171,8 @@ impl FromStr for ParseMode {
#[cfg(test)]
mod tests {
#![allow(deprecated)]
use super::*;
#[test]

View file

@ -10,6 +10,11 @@ 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,

View file

@ -10,6 +10,11 @@ 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,
@ -26,10 +31,11 @@ mod tests {
#[test]
fn deserialize() {
let json = r#"{"file_id":"id","width":320,"height":320,
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),

View file

@ -11,6 +11,11 @@ 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,

View file

@ -11,6 +11,11 @@ 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,

View file

@ -15,6 +15,11 @@ 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,

View file

@ -9,6 +9,11 @@ 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,