Resolved conflict

This commit is contained in:
Mr-Andersen 2019-09-03 20:32:48 +03:00
commit e41d62a27f
16 changed files with 415 additions and 159 deletions

View file

@ -1,3 +1,3 @@
pub mod network; mod network;
pub mod requests; pub mod requests;
pub mod types; pub mod types;

View file

@ -1,17 +1,16 @@
use apply::Apply;
use futures::compat::Future01CompatExt; use futures::compat::Future01CompatExt;
use reqwest::r#async::{multipart::Form, Client};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde_json::Value; use serde_json::Value;
use reqwest::{
r#async::{Client, multipart::Form},
StatusCode,
};
use apply::Apply;
use crate::core::requests::{RequestError, ResponseResult};
const TELEGRAM_API_URL: &str = "https://api.telegram.org"; const TELEGRAM_API_URL: &str = "https://api.telegram.org";
/// Creates URL for making HTTPS requests. See the [Telegram documentation].
/// Create url for macking requests, see [telegram docs](https://core.telegram.org/bots/api#making-requests) ///
/// [Telegram documentation]: https://core.telegram.org/bots/api#making-requests
fn method_url(base: &str, token: &str, method_name: &str) -> String { fn method_url(base: &str, token: &str, method_name: &str) -> String {
format!( format!(
"{url}/bot{token}/{method}", "{url}/bot{token}/{method}",
@ -21,7 +20,9 @@ fn method_url(base: &str, token: &str, method_name: &str) -> String {
) )
} }
/// Create url for downloading file, see [telegram docs](https://core.telegram.org/bots/api#file) /// 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 { fn file_url(base: &str, token: &str, file_path: &str) -> String {
format!( format!(
"{url}/file/bot{token}/{file}", "{url}/file/bot{token}/{file}",
@ -31,33 +32,6 @@ fn file_url(base: &str, token: &str, file_path: &str) -> String {
) )
} }
#[derive(Debug, Display)]
pub enum RequestError {
#[display(fmt = "Telegram error #{}: {}", status_code, description)]
ApiError {
status_code: StatusCode,
description: String,
},
#[display(fmt = "Network error: {err}", err = _0)]
NetworkError(reqwest::Error),
#[display(fmt = "InvalidJson error caused by: {err}", err = _0)]
InvalidJson(serde_json::Error),
}
impl std::error::Error for RequestError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
RequestError::ApiError { .. } => None,
RequestError::NetworkError(err) => Some(err),
RequestError::InvalidJson(err) => Some(err),
}
}
}
pub type ResponseResult<T> = Result<T, RequestError>;
pub async fn request<T: DeserializeOwned>( pub async fn request<T: DeserializeOwned>(
client: &Client, client: &Client,
token: &str, token: &str,
@ -66,12 +40,9 @@ pub async fn request<T: DeserializeOwned>(
) -> ResponseResult<T> { ) -> ResponseResult<T> {
let mut response = client let mut response = client
.post(&method_url(TELEGRAM_API_URL, token, method_name)) .post(&method_url(TELEGRAM_API_URL, token, method_name))
.apply(|request_builder| { .apply(|request_builder| match params {
if let Some(params) = params { Some(params) => request_builder.multipart(params),
request_builder.multipart(params) None => request_builder,
} else {
request_builder
}
}) })
.send() .send()
.compat() .compat()

View file

@ -1,4 +0,0 @@
pub enum ParseMode {
HTML,
Markdown,
}

View file

@ -38,8 +38,8 @@ impl FormBuilder {
form: self.form.text( form: self.form.text(
name.to_owned(), name.to_owned(),
serde_json::to_string(value).expect("serde_json::to_string failed"), serde_json::to_string(value).expect("serde_json::to_string failed"),
) ),
} },
} }
} }

View file

@ -1,25 +1,18 @@
use crate::core::{ use crate::core::network;
types::User, use crate::core::requests::{Request, RequestFuture, RequestInfo, ResponseResult};
network::{ use crate::core::types::User;
request, ResponseResult,
},
requests::{
Request, RequestInfo, RequestFuture,
}
};
#[derive(Debug, Constructor)] #[derive(Debug, Constructor)]
pub struct GetMe { pub struct GetMe {
info: RequestInfo info: RequestInfo,
} }
impl Request for GetMe { impl Request for GetMe {
type ReturnValue = User; type ReturnValue = User;
fn send(self) -> RequestFuture<ResponseResult<Self::ReturnValue>> { fn send(self) -> RequestFuture<ResponseResult<Self::ReturnValue>> {
Box::new(async move { Box::pin(async move {
request(&self.info.client, &self.info.token, "getMe", None).await network::request(&self.info.client, &self.info.token, "getMe", None).await
}) })
} }
} }

View file

@ -1,13 +1,38 @@
use std::future::Future; use std::future::Future;
use std::pin::Pin;
use crate::core::network::ResponseResult;
use serde::de::DeserializeOwned;
use reqwest::r#async::Client; use reqwest::r#async::Client;
use reqwest::StatusCode;
use serde::de::DeserializeOwned;
mod form_builder; mod form_builder;
#[derive(Debug, Display)]
pub enum RequestError {
#[display(fmt = "Telegram error #{}: {}", status_code, description)]
ApiError {
status_code: StatusCode,
description: String,
},
#[display(fmt = "Network error: {err}", err = _0)]
NetworkError(reqwest::Error),
#[display(fmt = "InvalidJson error caused by: {err}", err = _0)]
InvalidJson(serde_json::Error),
}
impl std::error::Error for RequestError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
RequestError::ApiError { .. } => None,
RequestError::NetworkError(err) => Some(err),
RequestError::InvalidJson(err) => Some(err),
}
}
}
pub type ResponseResult<T> = Result<T, RequestError>;
/// Request that can be sent to telegram. /// Request that can be sent to telegram.
/// `ReturnValue` - a type that will be returned from Telegram. /// `ReturnValue` - a type that will be returned from Telegram.
@ -18,7 +43,9 @@ pub trait Request {
fn send(self) -> RequestFuture<ResponseResult<Self::ReturnValue>>; fn send(self) -> RequestFuture<ResponseResult<Self::ReturnValue>>;
} }
pub type RequestFuture<T> = Box<dyn Future<Output = T>>; pub type RequestFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
// todo: better name? // todo: better name?
#[derive(Debug)] #[derive(Debug)]
@ -41,3 +68,7 @@ pub enum ChatId {
pub mod get_me; pub mod get_me;
pub mod send_message; pub mod send_message;
fn a() {
use futures;
}

View file

@ -1,17 +1,6 @@
use crate::core::{ use crate::core::requests::form_builder::FormBuilder;
types::Message, use crate::core::requests::{ChatId, Request, RequestFuture, RequestInfo, ResponseResult};
network::{ use crate::core::{network, types::Message};
request, ResponseResult,
},
requests::{
form_builder::FormBuilder,
ChatId,
Request,
RequestInfo,
RequestFuture,
}
};
#[derive(Debug, TypedBuilder)] #[derive(Debug, TypedBuilder)]
pub struct SendMessage { pub struct SendMessage {
@ -35,9 +24,8 @@ pub struct SendMessage {
impl Request for SendMessage { impl Request for SendMessage {
type ReturnValue = Message; type ReturnValue = Message;
fn send(self) -> RequestFuture<ResponseResult<Self::ReturnValue>> { fn send(self) -> RequestFuture<ResponseResult<Self::ReturnValue>> {
Box::new(async move { Box::pin(async move {
let params = FormBuilder::new() let params = FormBuilder::new()
.add("chat_id", &self.chat_id) .add("chat_id", &self.chat_id)
.add("text", &self.text) .add("text", &self.text)
@ -50,7 +38,13 @@ impl Request for SendMessage {
.add_if_some("reply_to_message_id", self.reply_to_message_id.as_ref()) .add_if_some("reply_to_message_id", self.reply_to_message_id.as_ref())
.build(); .build();
request(&self.info.client, &self.info.token, "sendMessage", Some(params)).await network::request(
&self.info.client,
&self.info.token,
"sendMessage",
Some(params),
)
.await
}) })
} }
} }

View file

@ -1,4 +1,5 @@
use serde::Deserialize; use serde::Deserialize;
use crate::core::types::user::User; use crate::core::types::user::User;
#[derive(Debug, Deserealize)] #[derive(Debug, Deserealize)]

View file

@ -1,8 +1,25 @@
use serde::Deserialize; use serde::Deserialize;
#[derive(Debug, Deserialize, Hash, PartialEq, Eq)] #[derive(Debug, Hash, PartialEq, Eq)]
pub enum InputFile { pub enum InputFile {
File(std::fs::File), File(std::path::PathBuf),
Url(String), Url(String),
FileId(String), FileId(String),
} }
impl serde::Serialize for InputFile {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 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::Url(url) => serializer.serialize_str(url),
InputFile::FileId(id) => serializer.serialize_str(id),
}
}
}

View file

@ -1,69 +1,245 @@
use serde::Deserialize; use serde::Deserialize;
use crate::core::types::InputFile; use crate::core::types::{InputFile, ParseMode};
// TODO: should variants use new-type?
#[derive(Debug, Serialize, PartialEq, Eq)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
/// This object represents the content of a media message to be sent.
/// [More](https://core.telegram.org/bots/api#inputmedia)
pub enum InputMedia { pub enum InputMedia {
InputMediaPhoto(InputMediaPhoto), /// Represents a photo to be sent.
InputMediaVideo(InputMediaVideo), Photo {
InputMediaAnimation(InputMediaAnimation), /// File to send.
InputMediaAudio(InputMediaAudiotype), media: InputFile,
InputMediaDocument(InputMediaDocument), /// Caption of the photo to be sent, 0-1024 characters
#[serde(skip_serializing_if = "Option::is_none")]
caption: Option<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]: crate::core::types::ParseMode::Markdown
/// [Html]: crate::core::types::ParseMode::Html
/// [bold, italic, fixed-width text or inline URLs]: crate::core::types::ParseMode
#[serde(skip_serializing_if = "Option::is_none")]
parse_mode: Option<ParseMode>,
},
Video {
/// File to send.File to send.
media: InputFile,
#[serde(skip_serializing_if = "Option::is_none")]
/// 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 thumbnails width and height should not exceed 320.
/// Ignored if the file is not uploaded using [InputFile::File].
///
/// [InputFile::File]: crate::core::types::InputFile::File
thumb: Option<InputFile>,
/// Caption of the video to be sent, 0-1024 characters.
#[serde(skip_serializing_if = "Option::is_none")]
caption: Option<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]: crate::core::types::ParseMode::Markdown
/// [Html]: crate::core::types::ParseMode::Html
/// [bold, italic, fixed-width text or inline URLs]: crate::core::types::ParseMode
#[serde(skip_serializing_if = "Option::is_none")]
parse_mode: Option<ParseMode>,
/// Video width
#[serde(skip_serializing_if = "Option::is_none")]
width: Option<u16>,
/// Video height
#[serde(skip_serializing_if = "Option::is_none")]
height: Option<u16>,
/// Video duration
#[serde(skip_serializing_if = "Option::is_none")]
duration: Option<u16>,
/// Pass `true`, if the uploaded video is suitable for streaming
#[serde(skip_serializing_if = "Option::is_none")]
supports_streaming: Option<bool>,
},
/// Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent.
Animation {
/// File to send.
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 thumbnails width and height should not exceed 320.
/// Ignored if the file is not uploaded using [InputFile::File].
///
/// [InputFile::File]: crate::core::types::InputFile::File
#[serde(skip_serializing_if = "Option::is_none")]
thumb: Option<InputFile>,
/// Caption of the animation to be sent, 0-1024 characters
#[serde(skip_serializing_if = "Option::is_none")]
caption: Option<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]: crate::core::types::ParseMode::Markdown
/// [Html]: crate::core::types::ParseMode::Html
/// [bold, italic, fixed-width text or inline URLs]: crate::core::types::ParseMode
#[serde(skip_serializing_if = "Option::is_none")]
parse_mode: Option<ParseMode>,
/// Animation width
#[serde(skip_serializing_if = "Option::is_none")]
width: Option<u16>,
/// Animation height
#[serde(skip_serializing_if = "Option::is_none")]
height: Option<u16>,
/// Animation duration
#[serde(skip_serializing_if = "Option::is_none")]
duration: Option<u16>,
},
/// Represents an audio file to be treated as music to be sent.
Audio {
/// File to send,
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 thumbnails width and height should not exceed 320.
/// Ignored if the file is not uploaded using [InputFile::File].
///
/// [InputFile::File]: crate::core::types::InputFile::File
#[serde(skip_serializing_if = "Option::is_none")]
thumb: Option<InputFile>,
/// Caption of the audio to be sent, 0-1024 characters
#[serde(skip_serializing_if = "Option::is_none")]
caption: Option<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]: crate::core::types::ParseMode::Markdown
/// [Html]: crate::core::types::ParseMode::Html
/// [bold, italic, fixed-width text or inline URLs]: crate::core::types::ParseMode
#[serde(skip_serializing_if = "Option::is_none")]
parse_mode: Option<String>,
/// Duration of the audio in seconds
#[serde(skip_serializing_if = "Option::is_none")]
duration: Option<u16>,
/// Performer of the audio
#[serde(skip_serializing_if = "Option::is_none")]
performer: Option<String>,
/// Title of the audio
#[serde(skip_serializing_if = "Option::is_none")]
title: Option<String>
},
/// Represents a general file to be sent.
Document {
/// File to send.
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 thumbnails width and height should not exceed 320.
/// Ignored if the file is not uploaded using [InputFile::File].
///
/// [InputFile::File]: crate::core::types::InputFile::File
#[serde(skip_serializing_if = "Option::is_none")]
thumb: Option<InputFile>,
/// Caption of the document to be sent, 0-1024 characters
#[serde(skip_serializing_if = "Option::is_none")]
caption: Option<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]: crate::core::types::ParseMode::Markdown
/// [Html]: crate::core::types::ParseMode::Html
/// [bold, italic, fixed-width text or inline URLs]: crate::core::types::ParseMode
#[serde(skip_serializing_if = "Option::is_none")]
parse_mode: Option<ParseMode>,
},
} }
pub enum ThumbKind { #[cfg(test)]
InputFile, mod tests {
String, use super::*;
}
#[derive(Debug, Serialize, Deserialize)] #[test]
pub struct InputMediaPhoto { fn photo_serialize() {
type_: String, let expected_json = r#"{"type":"photo","media":"123456"}"#;
media: String, let photo = InputMedia::Photo {
caption: Option<String>, media: InputFile::FileId(String::from("123456")),
parse_mode: Option<String>, caption: None,
} parse_mode: None,
};
#[derive(Debug, Serialize), Deserialize] let actual_json = serde_json::to_string(&photo).unwrap();
pub struct InputMediaVideo { assert_eq!(expected_json, actual_json);
type_: String, }
media: String,
thumb: ThumbKind,
caption: Option<String>,
parse_mode: Option<String>,
width: Option<i64>,
height: Option<i64>,
duration: Option<i64>,
supports_streaming: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)] #[test]
pub struct InputMediaAnimation { fn video_serialize() {
type_: String, let expected_json = r#"{"type":"video","media":"123456"}"#;
media: String, let video = InputMedia::Video {
thumb: Option<ThumbKind>, media: InputFile::FileId(String::from("123456")),
caption: Option<String>, thumb: None,
parse_mode: Option<String>, caption: None,
width: Option<i64>, parse_mode: None,
height: Option<i64>, width: None,
duration: Option<i64>, height: None,
} duration: None,
supports_streaming: None,
};
#[derive(Debug, Serialize, Deserialize)] let actual_json = serde_json::to_string(&video).unwrap();
pub struct InputMediaAudio { assert_eq!(expected_json, actual_json);
type_: String, }
media: String,
thumb: Option<ThumbKind>,
caption: Option<String>,
parse_mode: Option<String>,
duration: Option<i64>,
performer: Option<i64>,
title: Option<String>
}
#[derive(Debug, Serialize, Deserialize)] #[test]
pub struct InputMediaDocument { fn animation_serialize() {
type_: String, let expected_json = r#"{"type":"animation","media":"123456"}"#;
media: String, let video = InputMedia::Animation {
thumb: Option<ThumbKind>, media: InputFile::FileId(String::from("123456")),
caption: Option<String>, thumb: None,
parse_mode: parse_mode, 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 {
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 {
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);
}
}

View file

@ -1,4 +1,5 @@
use serde::Serialize; use serde::Serialize;
use crate::core::parse_mode::ParseMode; use crate::core::parse_mode::ParseMode;
pub enum InputMessageContent { pub enum InputMessageContent {

View file

@ -1,11 +1,9 @@
// use serde::Deserialize; // use serde::Deserialize;
use crate::core::types::{ use crate::core::types::{
Animation, Audio, Chat, Contact, Animation, Audio, Chat, Contact, Document, Game, InlineKeyboardMarkup, Invoice, Location,
Document, Game, Invoice, InlineKeyboardMarkup, MessageEntity, PassportData, PhotoSize, Poll, Sticker, SuccessfulPayment, User, Venue, Video,
PhotoSize, MessageEntity, Location, PassportData, Poll, VideoNote, Voice,
Sticker, SuccessfulPayment,
User, Video, VideoNote, Venue, Voice,
}; };
#[derive(Debug, Deserialize, Hash, PartialEq, Eq)] #[derive(Debug, Deserialize, Hash, PartialEq, Eq)]

View file

@ -1,6 +1,5 @@
use crate::core::types::User; use crate::core::types::User;
#[derive(Deserialize, Debug, PartialEq, Hash, Eq)] #[derive(Deserialize, Debug, PartialEq, Hash, Eq)]
pub struct MessageEntity { pub struct MessageEntity {
#[serde(flatten)] #[serde(flatten)]
@ -13,10 +12,19 @@ pub struct MessageEntity {
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum MessageEntityKind { pub enum MessageEntityKind {
Mention, Hashtag, Cashtag, BotCommand, Url, Email, PhoneNumber, Mention,
Bold, Italic, Code, Pre, Hashtag,
Cashtag,
BotCommand,
Url,
Email,
PhoneNumber,
Bold,
Italic,
Code,
Pre,
TextLink { url: String }, TextLink { url: String },
TextMention { user: User } TextMention { user: User },
} }
#[test] #[test]
@ -25,12 +33,13 @@ fn recursive_kind() {
assert_eq!( assert_eq!(
MessageEntity { MessageEntity {
kind: MessageEntityKind::TextLink { url: "ya.ru".into() }, kind: MessageEntityKind::TextLink {
url: "ya.ru".into()
},
offset: 1, offset: 1,
length: 2 length: 2
}, },
from_str::<MessageEntity>( from_str::<MessageEntity>(r#"{"type":"text_link","url":"ya.ru","offset":1,"length":2}"#)
r#"{"type":"text_link","url":"ya.ru","offset":1,"length":2}"# .unwrap()
).unwrap()
); );
} }

View file

@ -1,4 +1,3 @@
mod not_implemented_types;
use self::not_implemented_types::*; use self::not_implemented_types::*;
@ -28,6 +27,9 @@ pub use self::{
sticker::Sticker, sticker::Sticker,
successful_payment::SuccessfulPayment, successful_payment::SuccessfulPayment,
user::User, user::User,
input_file::InputFile,
input_media::InputMedia,
parse_mode::ParseMode,
}; };
mod answer_pre_checkout_query; mod answer_pre_checkout_query;
@ -42,6 +44,7 @@ mod invoice;
mod label_price; mod label_price;
mod message; mod message;
mod message_entity; mod message_entity;
mod not_implemented_types;
mod order_info; mod order_info;
mod pre_checkout_query; mod pre_checkout_query;
mod send_invoice; mod send_invoice;
@ -51,3 +54,6 @@ mod shipping_query;
mod sticker; mod sticker;
mod successful_payment; mod successful_payment;
mod user; mod user;
mod input_file;
mod input_media;
mod parse_mode;

View file

@ -0,0 +1,63 @@
use serde::{Serialize, Deserialize};
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
/// ## 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 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=<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.
/// - 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`.
///
/// ## Markdown style
/// To use this mode, pass [Markdown] in the `parse_mode` field when using [SendMessage] (or other methods).
///
/// Use the following syntax in your message:
///
/// ```ignore
/// *bold text*
/// _italic text_
/// [inline URL](http://www.example.com/)
/// [inline mention of a user](tg://user?id=123456789)
/// `inline fixed-width code`
/// &#96;&#96;&#96;block_language
/// pre-formatted fixed-width code block
/// &#96;&#96;&#96;
/// ```
///
/// ## HTML style
/// To use this mode, pass [HTML] in the `parse_mode` field when using [SendMessage] (or other methods).
///
/// The following tags are currently supported:
///
/// ```ignore
/// <b>bold</b>, <strong>bold</strong>
/// <i>italic</i>, <em>italic</em>
/// <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>
/// ```
///
/// 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;`.
pub enum ParseMode {
HTML,
Markdown,
}

View file

@ -5,4 +5,4 @@ extern crate serde;
#[macro_use] #[macro_use]
extern crate typed_builder; extern crate typed_builder;
mod core; pub mod core;