mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-22 14:35:36 +01:00
Resolved conflict
This commit is contained in:
commit
e41d62a27f
16 changed files with 415 additions and 159 deletions
|
@ -1,3 +1,3 @@
|
||||||
pub mod network;
|
mod network;
|
||||||
pub mod requests;
|
pub mod requests;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
pub enum ParseMode {
|
|
||||||
HTML,
|
|
||||||
Markdown,
|
|
||||||
}
|
|
|
@ -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"),
|
||||||
)
|
),
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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 thumbnail‘s 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 thumbnail‘s 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 thumbnail‘s 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 thumbnail‘s 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
63
src/core/types/parse_mode.rs
Normal file
63
src/core/types/parse_mode.rs
Normal 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`
|
||||||
|
/// ```block_language
|
||||||
|
/// pre-formatted fixed-width code block
|
||||||
|
/// ```
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## 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 `<`, `>` with `>` and `&` with `&`).
|
||||||
|
/// - All numerical HTML entities are supported.
|
||||||
|
/// - The API currently supports only the following named HTML entities: `<`, `>`, `&` and `"`.
|
||||||
|
pub enum ParseMode {
|
||||||
|
HTML,
|
||||||
|
Markdown,
|
||||||
|
}
|
|
@ -5,4 +5,4 @@ extern crate serde;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate typed_builder;
|
extern crate typed_builder;
|
||||||
|
|
||||||
mod core;
|
pub mod core;
|
||||||
|
|
Loading…
Reference in a new issue