Merge branch 'dev' into cows_for_form_builder

This commit is contained in:
Waffle Lapkin 2019-11-02 02:12:34 +03:00 committed by GitHub
commit 06c44831b6
81 changed files with 2947 additions and 1000 deletions

View file

@ -1,5 +1,5 @@
[package]
name = "async-telegram-bot"
name = "telebofr"
version = "0.1.0"
edition = "2018"

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019 async-telegram-bot
Copyright (c) 2019 telebofr
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,30 +1,20 @@
<div align="center">
<h1>async-telegram-bot</h1>
<img src="ICON.png" width="200"/>
<h1>telebofr</h1>
<a href="https://docs.rs/async-telegram-bot/">
<a href="https://docs.rs/telebofr/">
<img src="https://img.shields.io/badge/docs.rs-link-blue.svg">
</a>
<a href="https://travis-ci.com/async-telegram-bot/async-telegram-bot">
<img src="https://travis-ci.com/async-telegram-bot/async-telegram-bot.svg?branch=dev" />
<a href="https://travis-ci.com/telebofr/telebofr">
<img src="https://travis-ci.com/telebofr/telebofr.svg?branch=dev" />
</a>
<a href="LICENSE">
<img src="https://img.shields.io/badge/license-MIT-blue.svg">
</a>
<a href="https://crates.io/crates/async-telegram-bot">
<a href="https://crates.io/crates/telebofr">
<img src="https://img.shields.io/badge/crates.io-v0.1.0-orange.svg">
</a>
<br>
<img src="ICON.png" width="300"/>
<br>
A full-featured framework that empowers you to easily build [Telegram bots](https://telegram.org/blog/bot-revolution) using the [`async`/`.await`](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html) syntax in [Rust](https://www.rust-lang.org/). It handles all the difficult stuff so you can focus only on your business logic.
</div>
## A simple bot
```rust
fn main() {
let bot = Bot::new(API_TOKEN).bla().bla();
}
```

View file

@ -1,4 +1,5 @@
format_code_in_doc_comments = true
wrap_comments = true
format_strings = true
max_width = 80
max_width = 80
merge_imports = true

View file

@ -1,24 +1,28 @@
use crate::{
bot::Bot,
requests::{
AnswerPreCheckoutQuery, AnswerShippingQuery, EditMessageLiveLocation,
ForwardMessage, GetFile, GetMe, KickChatMember, PinChatMessage,
PromoteChatMember, RestrictChatMember, SendAudio, SendChatAction,
SendContact, SendLocation, SendMediaGroup, SendMessage, SendPhoto,
SendPoll, SendVenue, SendVideoNote, SendVoice, StopMessageLiveLocation,
UnbanChatMember, UnpinChatMessage, GetUpdates
AnswerCallbackQuery, AnswerPreCheckoutQuery, AnswerShippingQuery,
DeleteChatPhoto, DeleteChatStickerSet, EditMessageLiveLocation,
ExportCharInviteLink, ForwardMessage, GetChat, GetChatAdministrators,
GetChatMember, GetChatMembersCount, GetFile, GetMe, GetUpdates,
KickChatMember, LeaveChat, PinChatMessage, PromoteChatMember,
RestrictChatMember, SendAnimation, SendAudio, SendChatAction,
SendContact, SendDocument, SendLocation, SendMediaGroup, SendMessage,
SendPhoto, SendPoll, SendVenue, SendVideo, SendVideoNote, SendVoice,
SetChatDescription, SetChatPermissions, SetChatPhoto,
SetChatStickerSet, SetChatTitle, StopMessageLiveLocation,
UnbanChatMember, UnpinChatMessage,
},
types::{ChatAction, ChatId, ChatPermissions, InputFile, InputMedia},
};
/// Telegram functions
impl Bot {
pub fn get_me(&self) -> GetMe {
GetMe::new(self.ctx())
GetMe::new(self)
}
pub fn get_updates(&self) -> GetUpdates {
GetUpdates::new(self.ctx())
GetUpdates::new(self)
}
pub fn send_message<C, T>(&self, chat_id: C, text: T) -> SendMessage
@ -26,7 +30,7 @@ impl Bot {
C: Into<ChatId>,
T: Into<String>,
{
SendMessage::new(self.ctx(), chat_id, text)
SendMessage::new(self, chat_id, text)
}
pub fn edit_message_live_location<Lt, Lg>(
@ -38,7 +42,7 @@ impl Bot {
Lt: Into<f64>,
Lg: Into<f64>,
{
EditMessageLiveLocation::new(self.ctx(), latitude, longitude)
EditMessageLiveLocation::new(self, latitude, longitude)
}
pub fn forward_message<C, F, M>(
@ -52,7 +56,7 @@ impl Bot {
F: Into<ChatId>,
M: Into<i32>,
{
ForwardMessage::new(self.ctx(), chat_id, from_chat_id, message_id)
ForwardMessage::new(self, chat_id, from_chat_id, message_id)
}
pub fn send_audio<C, A>(&self, chat_id: C, audio: A) -> SendAudio
@ -60,7 +64,7 @@ impl Bot {
C: Into<ChatId>,
A: Into<InputFile>,
{
SendAudio::new(self.ctx(), chat_id, audio)
SendAudio::new(self, chat_id, audio)
}
pub fn send_location<C, Lt, Lg>(
@ -74,7 +78,7 @@ impl Bot {
Lt: Into<f64>,
Lg: Into<f64>,
{
SendLocation::new(self.ctx(), chat_id, latitude, longitude)
SendLocation::new(self, chat_id, latitude, longitude)
}
pub fn send_media_group<C, M>(&self, chat_id: C, media: M) -> SendMediaGroup
@ -82,7 +86,7 @@ impl Bot {
C: Into<ChatId>,
M: Into<Vec<InputMedia>>,
{
SendMediaGroup::new(self.ctx(), chat_id, media)
SendMediaGroup::new(self, chat_id, media)
}
pub fn send_photo<C, P>(&self, chat_id: C, photo: P) -> SendPhoto
@ -90,18 +94,18 @@ impl Bot {
C: Into<ChatId>,
P: Into<InputFile>,
{
SendPhoto::new(self.ctx(), chat_id, photo)
SendPhoto::new(self, chat_id, photo)
}
pub fn stop_message_live_location(&self) -> StopMessageLiveLocation {
StopMessageLiveLocation::new(self.ctx())
StopMessageLiveLocation::new(self)
}
pub fn get_file<F>(&self, file_id: F) -> GetFile
where
F: Into<String>,
{
GetFile::new(self.ctx(), file_id)
GetFile::new(self, file_id)
}
pub fn answer_pre_checkout_query<I, O>(
@ -113,7 +117,14 @@ impl Bot {
I: Into<String>,
O: Into<bool>,
{
AnswerPreCheckoutQuery::new(self.ctx(), pre_checkout_query_id, ok)
AnswerPreCheckoutQuery::new(self, pre_checkout_query_id, ok)
}
pub fn get_chat<I>(&self, chat_id: I) -> GetChat
where
I: Into<ChatId>,
{
GetChat::new(self, chat_id)
}
pub fn answer_shipping_query<I, O>(
@ -125,7 +136,7 @@ impl Bot {
I: Into<String>,
O: Into<bool>,
{
AnswerShippingQuery::new(self.ctx(), shipping_query_id, ok)
AnswerShippingQuery::new(self, shipping_query_id, ok)
}
pub fn kick_chat_member<C, U>(
@ -137,7 +148,7 @@ impl Bot {
C: Into<ChatId>,
U: Into<i32>,
{
KickChatMember::new(self.ctx(), chat_id, user_id)
KickChatMember::new(self, chat_id, user_id)
}
pub fn pin_chat_message<C, M>(
@ -149,7 +160,7 @@ impl Bot {
C: Into<ChatId>,
M: Into<i32>,
{
PinChatMessage::new(self.ctx(), chat_id, message_id)
PinChatMessage::new(self, chat_id, message_id)
}
pub fn promote_chat_member<C, U>(
@ -161,7 +172,7 @@ impl Bot {
C: Into<ChatId>,
U: Into<i32>,
{
PromoteChatMember::new(self.ctx(), chat_id, user_id)
PromoteChatMember::new(self, chat_id, user_id)
}
pub fn restrict_chat_member<C, U, P>(
@ -175,7 +186,7 @@ impl Bot {
U: Into<i32>,
P: Into<ChatPermissions>,
{
RestrictChatMember::new(self.ctx(), chat_id, user_id, permissions)
RestrictChatMember::new(self, chat_id, user_id, permissions)
}
pub fn send_chat_action<C, A>(
@ -187,7 +198,7 @@ impl Bot {
C: Into<ChatId>,
A: Into<ChatAction>,
{
SendChatAction::new(self.ctx(), chat_id, action)
SendChatAction::new(self, chat_id, action)
}
pub fn send_contact<C, P, F>(
@ -201,7 +212,7 @@ impl Bot {
P: Into<String>,
F: Into<String>,
{
SendContact::new(self.ctx(), chat_id, phone_number, first_name)
SendContact::new(self, chat_id, phone_number, first_name)
}
pub fn send_poll<C, Q, O>(
@ -215,7 +226,7 @@ impl Bot {
Q: Into<String>,
O: Into<Vec<String>>,
{
SendPoll::new(self.ctx(), chat_id, question, options)
SendPoll::new(self, chat_id, question, options)
}
pub fn send_venue<C, Lt, Lg, T, A>(
@ -233,7 +244,7 @@ impl Bot {
T: Into<String>,
A: Into<String>,
{
SendVenue::new(self.ctx(), chat_id, latitude, longitude, title, address)
SendVenue::new(self, chat_id, latitude, longitude, title, address)
}
pub fn send_video_note<C, V>(
@ -243,17 +254,24 @@ impl Bot {
) -> SendVideoNote
where
C: Into<ChatId>,
V: Into<String>, // TODO: InputFile
V: Into<InputFile>,
{
SendVideoNote::new(self.ctx(), chat_id, video_note)
SendVideoNote::new(self, chat_id, video_note)
}
pub fn send_voice<C, V>(&self, chat_id: C, voice: V) -> SendVoice
where
C: Into<ChatId>,
V: Into<String>, // TODO: InputFile
V: Into<InputFile>,
{
SendVoice::new(self.ctx(), chat_id, voice)
SendVoice::new(self, chat_id, voice)
}
pub fn send_chat_description<C>(&self, chat_id: C) -> SetChatDescription
where
C: Into<ChatId>,
{
SetChatDescription::new(self, chat_id)
}
pub fn unban_chat_member<C, U>(
@ -265,13 +283,144 @@ impl Bot {
C: Into<ChatId>,
U: Into<i32>,
{
UnbanChatMember::new(self.ctx(), chat_id, user_id)
UnbanChatMember::new(self, chat_id, user_id)
}
pub fn unpin_chat_message<C>(&self, chat_id: C) -> UnpinChatMessage
where
C: Into<ChatId>,
{
UnpinChatMessage::new(self.ctx(), chat_id)
UnpinChatMessage::new(self, chat_id)
}
pub fn answer_callback_query<S>(
&self,
callback_query_id: S,
) -> AnswerCallbackQuery
where
S: Into<String>,
{
AnswerCallbackQuery::new(self, callback_query_id)
}
pub fn delete_chat_sticker_set<C>(&self, chat_id: C) -> DeleteChatStickerSet
where
C: Into<ChatId>,
{
DeleteChatStickerSet::new(self, chat_id)
}
pub fn set_chat_sticker_set<C, S>(
&self,
chat_id: C,
sticker_set_name: S,
) -> SetChatStickerSet
where
C: Into<ChatId>,
S: Into<String>,
{
SetChatStickerSet::new(self, chat_id, sticker_set_name)
}
pub fn get_chat_member<C, I>(&self, chat_id: C, user_id: I) -> GetChatMember
where
C: Into<ChatId>,
I: Into<i32>,
{
GetChatMember::new(self, chat_id, user_id)
}
pub fn get_chat_administrators<C, I>(
&self,
chat_id: C,
) -> GetChatAdministrators
where
C: Into<ChatId>,
{
GetChatAdministrators::new(self, chat_id)
}
pub fn get_chat_members_count<C>(&self, chat_id: C) -> GetChatMembersCount
where
C: Into<ChatId>,
{
GetChatMembersCount::new(self, chat_id)
}
pub fn send_video<C, V>(&self, chat_id: C, video: V) -> SendVideo
where
C: Into<ChatId>,
V: Into<InputFile>,
{
SendVideo::new(self, chat_id, video)
}
pub fn send_document<C, D>(&self, chat_id: C, document: D) -> SendDocument
where
C: Into<ChatId>,
D: Into<InputFile>,
{
SendDocument::new(self, chat_id, document)
}
pub fn send_animation<C, S>(
&self,
chat_id: C,
animation: S,
) -> SendAnimation
where
C: Into<ChatId>,
S: Into<InputFile>,
{
SendAnimation::new(self, chat_id, animation)
}
pub fn set_chat_title<C, T>(&self, chat_id: C, title: T) -> SetChatTitle
where
C: Into<ChatId>,
T: Into<String>,
{
SetChatTitle::new(self, chat_id, title)
}
pub fn delete_chat_photo<C>(&self, chat_id: C) -> DeleteChatPhoto
where
C: Into<ChatId>,
{
DeleteChatPhoto::new(self, chat_id)
}
pub fn leave_chat<C>(&self, chat_id: C) -> LeaveChat
where
C: Into<ChatId>,
{
LeaveChat::new(self, chat_id)
}
pub fn set_chat_photo<C, P>(&self, chat_id: C, photo: P) -> SetChatPhoto
where
C: Into<ChatId>,
P: Into<InputFile>,
{
SetChatPhoto::new(self, chat_id, photo)
}
pub fn export_chat_invite_link<C>(&self, chat_id: C) -> ExportCharInviteLink
where
C: Into<ChatId>,
{
ExportCharInviteLink::new(self, chat_id)
}
pub fn set_chat_permissions<C, CP>(
&self,
chat_id: C,
permissions: CP,
) -> SetChatPermissions
where
C: Into<ChatId>,
CP: Into<ChatPermissions>,
{
SetChatPermissions::new(self, chat_id, permissions)
}
}

View file

@ -8,7 +8,7 @@ use crate::network::download_file_stream;
use crate::{bot::Bot, network::download_file, DownloadError};
impl Bot {
/// Download file from telegram into `destination`.
/// Download a file from Telegram into `destination`.
/// `path` can be obtained from [`get_file`] method.
///
/// For downloading as Stream of Chunks see [`download_file_stream`].
@ -16,11 +16,10 @@ impl Bot {
/// ## Examples
///
/// ```no_run
/// use async_telegram_bot::{
/// bot::Bot, requests::Request, types::File as TgFile,
/// };
/// use telebofr::types::File as TgFile;
/// use tokio::fs::File;
/// # use async_telegram_bot::RequestError;
/// # use telebofr::RequestError;
/// use telebofr::Bot;
///
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let bot = Bot::new("TOKEN");
@ -44,9 +43,9 @@ impl Bot {
download_file(&self.client, &self.token, path, destination).await
}
/// Download file from telegram.
/// Download a file from Telegram.
///
/// `path` can be obtained from [`get_file`] method.
/// `path` can be obtained from the [`get_file`] method.
///
/// For downloading into [`AsyncWrite`] (e.g. [`tokio::fs::File`])
/// see [`download_file`].

View file

@ -1,39 +1,45 @@
//! A Telegram bot.
use reqwest::Client;
use crate::requests::RequestContext;
mod api;
mod download;
/// A Telegram bot used to build requests.
#[derive(Debug, Clone)]
pub struct Bot {
token: String,
client: Client,
}
/// Constructors
impl Bot {
pub fn new(token: &str) -> Self {
pub fn new<S>(token: S) -> Self
where
S: Into<String>,
{
Bot {
token: String::from(token),
token: token.into(),
client: Client::new(),
}
}
pub fn with_client(token: &str, client: Client) -> Self {
pub fn with_client<S>(token: S, client: Client) -> Self
where
S: Into<String>,
{
Bot {
token: String::from(token),
token: token.into(),
client,
}
}
}
impl Bot {
fn ctx(&self) -> RequestContext {
RequestContext {
token: &self.token,
client: &self.client,
}
#[inline]
pub fn token(&self) -> &str {
&self.token
}
#[inline]
pub fn client(&self) -> &Client {
&self.client
}
}

View file

@ -1,9 +0,0 @@
//! Update dispatching.
pub mod filter;
pub mod handler;
pub mod simple;
pub mod updater;
pub use filter::Filter;
pub use handler::Handler;

View file

@ -1,304 +0,0 @@
pub mod error_policy;
use crate::{
dispatcher::{
filter::Filter,
handler::Handler,
updater::Updater,
},
types::{
Update,
Message,
UpdateKind,
CallbackQuery,
ChosenInlineResult,
},
};
use futures::StreamExt;
use crate::dispatcher::simple::error_policy::ErrorPolicy;
type Handlers<'a, T, E> = Vec<(Box<dyn Filter<T> + 'a>, Box<dyn Handler<'a, T, E> + 'a>)>;
/// Dispatcher that dispatches updates from telegram.
///
/// This is 'simple' implementation with following limitations:
/// - Error (`E` generic parameter) _must_ implement [`std::fmt::Debug`]
/// - All 'handlers' are boxed
/// - Handler's fututres are also boxed
/// - [Custom error policy] is also boxed
/// - All errors from [updater] are ignored (TODO: remove this limitation)
/// - All handlers executed in order (this means that in dispatcher have
/// 2 upadtes it will first execute some handler into complition with
/// first update and **then** search for handler for second update,
/// this is probably wrong)
///
/// ## Examples
///
/// Simplest example:
/// ```no_run
/// # async fn run() {
/// use std::convert::Infallible;
/// use async_telegram_bot::{
/// bot::Bot,
/// types::Message,
/// dispatcher::{
/// updater::polling,
/// simple::{Dispatcher, error_policy::ErrorPolicy},
/// },
/// };
///
/// async fn handle_edited_message(mes: Message) {
/// println!("Edited message: {:?}", mes)
/// }
///
/// let bot = Bot::new("TOKEN");
///
/// // create dispatcher which handlers can't fail
/// // with error policy that just ignores all errors (that can't ever happen)
/// let mut dp = Dispatcher::<Infallible>::new(ErrorPolicy::Ignore)
/// // Add 'handler' that will handle all messages sent to the bot
/// .message_handler(true, |mes: Message| async move {
/// println!("New message: {:?}", mes)
/// })
/// // Add 'handler' that will handle all
/// // messages edited in chat with the bot
/// .edited_message_handler(true, handle_edited_message);
///
/// // Start dispatching updates from long polling
/// dp.dispatch(polling(&bot)).await;
/// # }
/// ```
///
/// [`std::fmt::Debug`]: std::fmt::Debug
/// [Custom error policy]: crate::dispatcher::simple::error_policy::ErrorPolicy::Custom
/// [updater]: crate::dispatcher::updater
pub struct Dispatcher<'a, E> {
message_handlers: Handlers<'a, Message, E>,
edited_message_handlers: Handlers<'a, Message, E>,
channel_post_handlers: Handlers<'a, Message, E>,
edited_channel_post_handlers: Handlers<'a, Message, E>,
inline_query_handlers: Handlers<'a, (), E>,
chosen_inline_result_handlers: Handlers<'a, ChosenInlineResult, E>,
callback_query_handlers: Handlers<'a, CallbackQuery, E>,
error_policy: ErrorPolicy<'a, E>,
}
impl<'a, E> Dispatcher<'a, E>
where
E: std::fmt::Debug, // TODO: Is this really necessary?
{
pub fn new(error_policy: ErrorPolicy<'a, E>) -> Self {
Dispatcher {
message_handlers: Vec::new(),
edited_message_handlers: Vec::new(),
channel_post_handlers: Vec::new(),
edited_channel_post_handlers: Vec::new(),
inline_query_handlers: Vec::new(),
chosen_inline_result_handlers: Vec::new(),
callback_query_handlers: Vec::new(),
error_policy
}
}
pub fn message_handler<F, H>(mut self, filter: F, handler: H) -> Self
where
F: Filter<Message> + 'a,
H: Handler<'a, Message, E> + 'a,
{
self.message_handlers.push((Box::new(filter), Box::new(handler)));
self
}
pub fn edited_message_handler<F, H>(mut self, filter: F, handler: H) -> Self
where
F: Filter<Message> + 'a,
H: Handler<'a, Message, E> + 'a,
{
self.edited_message_handlers.push((Box::new(filter), Box::new(handler)));
self
}
pub fn channel_post_handler<F, H>(mut self, filter: F, handler: H) -> Self
where
F: Filter<Message> + 'a,
H: Handler<'a, Message, E> + 'a,
{
self.channel_post_handlers.push((Box::new(filter), Box::new(handler)));
self
}
pub fn edited_channel_post_handler<F, H>(mut self, filter: F, handler: H) -> Self
where
F: Filter<Message> + 'a,
H: Handler<'a, Message, E> + 'a,
{
self.edited_channel_post_handlers.push((Box::new(filter), Box::new(handler)));
self
}
pub fn inline_query_handler<F, H>(mut self, filter: F, handler: H) -> Self
where
F: Filter<()> + 'a,
H: Handler<'a, (), E> + 'a,
{
self.inline_query_handlers.push((Box::new(filter), Box::new(handler)));
self
}
pub fn chosen_inline_result_handler<F, H>(mut self, filter: F, handler: H) -> Self
where
F: Filter<ChosenInlineResult> + 'a,
H: Handler<'a, ChosenInlineResult, E> + 'a,
{
self.chosen_inline_result_handlers.push((Box::new(filter), Box::new(handler)));
self
}
pub fn callback_query_handler<F, H>(mut self, filter: F, handler: H) -> Self
where
F: Filter<CallbackQuery> + 'a,
H: Handler<'a, CallbackQuery, E> + 'a,
{
self.callback_query_handlers.push((Box::new(filter), Box::new(handler)));
self
}
// TODO: Can someone simplify this?
pub async fn dispatch<U, UE>(&mut self, updates: U)
where
U: Updater<UE> + 'a
{
updates.for_each(|res| {
async {
let res = res;
let Update { kind, id } = match res {
Ok(upd) => upd,
_ => return // TODO: proper error handling
};
log::debug!("Handled update#{id:?}: {kind:?}", id = id, kind = kind);
// TODO: can someone extract this to a function?
macro_rules! call {
($h:expr, $value:expr) => {{
let value = $value;
let handler = $h.iter().find_map(|e| {
let (filter, handler) = e;
if filter.test(&value) {
Some(handler)
} else {
None
}
});
match handler {
Some(handler) => {
if let Err(err) = handler.handle(value).await {
self.error_policy.handle_error(err).await;
}
},
None => log::warn!("Unhandled update: {:?}", value)
}
}};
}
match kind {
UpdateKind::Message(mes) => call!(self.message_handlers, mes),
UpdateKind::EditedMessage(mes) => call!(self.edited_message_handlers, mes),
UpdateKind::ChannelPost(post) => call!(self.channel_post_handlers, post),
UpdateKind::EditedChannelPost(post) => call!(self.edited_channel_post_handlers, post),
UpdateKind::InlineQuery(query) => call!(self.inline_query_handlers, query),
UpdateKind::ChosenInlineResult(result) => call!(self.chosen_inline_result_handlers, result),
UpdateKind::CallbackQuery(callback) => call!(self.callback_query_handlers, callback),
}
}
})
.await;
}
}
#[cfg(test)]
mod tests {
use std::convert::Infallible;
use std::sync::atomic::{AtomicI32, Ordering};
use crate::{
types::{
Message, ChatKind, MessageKind, Sender, ForwardKind, MediaKind, Chat, User, Update, UpdateKind
},
dispatcher::{simple::{Dispatcher, error_policy::ErrorPolicy}, updater::StreamUpdater},
};
use futures::Stream;
#[tokio::test]
async fn first_handler_executes_1_time() {
let counter = &AtomicI32::new(0);
let counter2 = &AtomicI32::new(0);
let mut dp = Dispatcher::<Infallible>::new(ErrorPolicy::Ignore)
.message_handler(true, |_mes: Message| async move {
counter.fetch_add(1, Ordering::SeqCst);
})
.message_handler(true, |_mes: Message| async move {
counter2.fetch_add(1, Ordering::SeqCst);
Ok::<_, Infallible>(())
});
dp.dispatch(one_message_updater()).await;
assert_eq!(counter.load(Ordering::SeqCst), 1);
assert_eq!(counter2.load(Ordering::SeqCst), 0);
}
fn message() -> Message {
Message {
id: 6534,
date: 1567898953,
chat: Chat {
id: 218485655,
photo: None,
kind: ChatKind::Private {
type_: (),
first_name: Some("W".to_string()),
last_name: None,
username: Some("WaffleLapkin".to_string()),
},
},
kind: MessageKind::Common {
from: Sender::User(User {
id: 457569668,
is_bot: true,
first_name: "BT".to_string(),
last_name: None,
username: Some("BloodyTestBot".to_string()),
language_code: None,
}),
forward_kind: ForwardKind::Origin {
reply_to_message: None,
},
edit_date: None,
media_kind: MediaKind::Text {
text: "text".to_string(),
entities: vec![],
},
reply_markup: None,
},
}
}
fn message_update() -> Update {
Update { id: 0, kind: UpdateKind::Message(message()) }
}
fn one_message_updater() -> StreamUpdater<impl Stream<Item=Result<Update, Infallible>>> {
use futures::future::ready;
use futures::stream;
StreamUpdater::new(
stream::once(ready(Ok(message_update())))
)
}
}

View file

@ -1,6 +1,4 @@
use std::pin::Pin;
use std::future::Future;
use std::fmt::Debug;
use std::{fmt::Debug, future::Future, pin::Pin};
// TODO: shouldn't it be trait?
pub enum ErrorPolicy<'a, E> {
@ -15,14 +13,12 @@ where
{
pub async fn handle_error(&self, error: E) {
match self {
Self::Ignore => {},
Self::Ignore => {}
Self::Log => {
// TODO: better message
log::error!("Error in handler: {:?}", error)
}
Self::Custom(func) => {
func(error).await
}
Self::Custom(func) => func(error).await,
}
}
@ -33,4 +29,4 @@ where
{
Self::Custom(Box::new(move |e| Box::pin(f(e))))
}
}
}

View file

@ -0,0 +1,373 @@
use futures::StreamExt;
use async_trait::async_trait;
use crate::{
dispatching::{
dispatchers::filter::error_policy::ErrorPolicy, filters::Filter,
handler::Handler, updater::Updater, Dispatcher,
},
types::{CallbackQuery, ChosenInlineResult, Message, Update, UpdateKind},
};
pub mod error_policy;
type Handlers<'a, T, E> =
Vec<(Box<dyn Filter<T> + 'a>, Box<dyn Handler<'a, T, E> + 'a>)>;
/// Dispatcher that dispatches updates from telegram.
///
/// This is 'filter' implementation with following limitations:
/// - Error (`E` generic parameter) _must_ implement [`std::fmt::Debug`]
/// - All 'handlers' are boxed
/// - Handler's fututres are also boxed
/// - [Custom error policy] is also boxed
/// - All errors from [updater] are ignored (TODO: remove this limitation)
/// - All handlers executed in order (this means that in dispatching have 2
/// upadtes it will first execute some handler into complition with first
/// update and **then** search for handler for second update, this is probably
/// wrong)
///
/// ## Examples
///
/// Simplest example:
/// ```no_run
/// # use telebofr::Bot;
/// use telebofr::types::Message;
/// async fn run() {
/// use std::convert::Infallible;
/// use telebofr::{
/// dispatching::{
/// dispatchers::filter::{error_policy::ErrorPolicy, FilterDispatcher},
/// updater::polling,
/// },
/// };
///
/// async fn handle_edited_message(mes: Message) {
/// println!("Edited message: {:?}", mes)
/// }
///
/// let bot = Bot::new("TOKEN");
///
/// // create dispatching which handlers can't fail
/// // with error policy that just ignores all errors (that can't ever happen)
/// let mut dp = FilterDispatcher::<Infallible>::new(ErrorPolicy::Ignore)
/// // Add 'handler' that will handle all messages sent to the bot
/// .message_handler(true, |mes: Message| {
/// async move { println!("New message: {:?}", mes) }
/// })
/// // Add 'handler' that will handle all
/// // messages edited in chat with the bot
/// .edited_message_handler(true, handle_edited_message);
///
/// // Start dispatching updates from long polling
/// dp.dispatch(polling(&bot)).await;
/// # }
/// ```
///
/// [`std::fmt::Debug`]: std::fmt::Debug
/// [Custom error policy]:
/// crate::dispatching::filter::error_policy::ErrorPolicy::Custom [updater]:
/// crate::dispatching::updater
pub struct FilterDispatcher<'a, E> {
message_handlers: Handlers<'a, Message, E>,
edited_message_handlers: Handlers<'a, Message, E>,
channel_post_handlers: Handlers<'a, Message, E>,
edited_channel_post_handlers: Handlers<'a, Message, E>,
inline_query_handlers: Handlers<'a, (), E>,
chosen_inline_result_handlers: Handlers<'a, ChosenInlineResult, E>,
callback_query_handlers: Handlers<'a, CallbackQuery, E>,
error_policy: ErrorPolicy<'a, E>,
}
impl<'a, E> FilterDispatcher<'a, E>
where
E: std::fmt::Debug, // TODO: Is this really necessary?
{
pub fn new(error_policy: ErrorPolicy<'a, E>) -> Self {
FilterDispatcher {
message_handlers: Vec::new(),
edited_message_handlers: Vec::new(),
channel_post_handlers: Vec::new(),
edited_channel_post_handlers: Vec::new(),
inline_query_handlers: Vec::new(),
chosen_inline_result_handlers: Vec::new(),
callback_query_handlers: Vec::new(),
error_policy,
}
}
pub fn message_handler<F, H>(mut self, filter: F, handler: H) -> Self
where
F: Filter<Message> + 'a,
H: Handler<'a, Message, E> + 'a,
{
self.message_handlers
.push((Box::new(filter), Box::new(handler)));
self
}
pub fn edited_message_handler<F, H>(mut self, filter: F, handler: H) -> Self
where
F: Filter<Message> + 'a,
H: Handler<'a, Message, E> + 'a,
{
self.edited_message_handlers
.push((Box::new(filter), Box::new(handler)));
self
}
pub fn channel_post_handler<F, H>(mut self, filter: F, handler: H) -> Self
where
F: Filter<Message> + 'a,
H: Handler<'a, Message, E> + 'a,
{
self.channel_post_handlers
.push((Box::new(filter), Box::new(handler)));
self
}
pub fn edited_channel_post_handler<F, H>(
mut self,
filter: F,
handler: H,
) -> Self
where
F: Filter<Message> + 'a,
H: Handler<'a, Message, E> + 'a,
{
self.edited_channel_post_handlers
.push((Box::new(filter), Box::new(handler)));
self
}
pub fn inline_query_handler<F, H>(mut self, filter: F, handler: H) -> Self
where
F: Filter<()> + 'a,
H: Handler<'a, (), E> + 'a,
{
self.inline_query_handlers
.push((Box::new(filter), Box::new(handler)));
self
}
pub fn chosen_inline_result_handler<F, H>(
mut self,
filter: F,
handler: H,
) -> Self
where
F: Filter<ChosenInlineResult> + 'a,
H: Handler<'a, ChosenInlineResult, E> + 'a,
{
self.chosen_inline_result_handlers
.push((Box::new(filter), Box::new(handler)));
self
}
pub fn callback_query_handler<F, H>(mut self, filter: F, handler: H) -> Self
where
F: Filter<CallbackQuery> + 'a,
H: Handler<'a, CallbackQuery, E> + 'a,
{
self.callback_query_handlers
.push((Box::new(filter), Box::new(handler)));
self
}
// TODO: Can someone simplify this?
pub async fn dispatch<U>(&mut self, updates: U)
where
U: Updater + 'a,
{
updates
.for_each(|res| {
async {
let res = res;
let Update { kind, id } = match res {
Ok(upd) => upd,
_ => return, // TODO: proper error handling
};
log::debug!(
"Handled update#{id:?}: {kind:?}",
id = id,
kind = kind
);
match kind {
UpdateKind::Message(mes) => {
self.handle(mes, &self.message_handlers).await
}
UpdateKind::EditedMessage(mes) => {
self.handle(mes, &self.edited_message_handlers)
.await;
}
UpdateKind::ChannelPost(post) => {
self.handle(post, &self.channel_post_handlers)
.await;
}
UpdateKind::EditedChannelPost(post) => {
self.handle(
post,
&self.edited_channel_post_handlers,
)
.await;
}
UpdateKind::InlineQuery(query) => {
self.handle(query, &self.inline_query_handlers)
.await;
}
UpdateKind::ChosenInlineResult(result) => {
self.handle(
result,
&self.chosen_inline_result_handlers,
)
.await;
}
UpdateKind::CallbackQuery(callback) => {
self.handle(
callback,
&self.callback_query_handlers,
)
.await;
}
}
}
})
.await;
}
async fn handle<T>(&self, update: T, handlers: &Handlers<'a, T, E>)
where
T: std::fmt::Debug,
{
let handler = handlers.iter().find_map(|e| {
let (filter, handler) = e;
if filter.test(&update) {
Some(handler)
} else {
None
}
});
match handler {
Some(handler) => {
if let Err(err) = handler.handle(update).await {
self.error_policy.handle_error(err).await
}
}
None => {
log::warn!("unhandled update {:?}", update);
}
}
}
}
#[async_trait(? Send)]
impl<'a, U, E> Dispatcher<'a, U> for FilterDispatcher<'a, E>
where
E: std::fmt::Debug,
U: Updater + 'a,
{
async fn dispatch(&'a mut self, updater: U) {
FilterDispatcher::dispatch(self, updater).await
}
}
#[cfg(test)]
mod tests {
use std::{
convert::Infallible,
sync::atomic::{AtomicI32, Ordering},
};
use futures::Stream;
use crate::{
dispatching::{
dispatchers::filter::{
error_policy::ErrorPolicy, FilterDispatcher,
},
updater::StreamUpdater,
},
types::{
Chat, ChatKind, ForwardKind, MediaKind, Message, MessageKind,
Sender, Update, UpdateKind, User,
},
};
#[tokio::test]
async fn first_handler_executes_1_time() {
let counter = &AtomicI32::new(0);
let counter2 = &AtomicI32::new(0);
let mut dp = FilterDispatcher::<Infallible>::new(ErrorPolicy::Ignore)
.message_handler(true, |_mes: Message| {
async move {
counter.fetch_add(1, Ordering::SeqCst);
}
})
.message_handler(true, |_mes: Message| {
async move {
counter2.fetch_add(1, Ordering::SeqCst);
Ok::<_, Infallible>(())
}
});
dp.dispatch(one_message_updater()).await;
assert_eq!(counter.load(Ordering::SeqCst), 1);
assert_eq!(counter2.load(Ordering::SeqCst), 0);
}
fn message() -> Message {
Message {
id: 6534,
date: 1567898953,
chat: Chat {
id: 218485655,
photo: None,
kind: ChatKind::Private {
type_: (),
first_name: Some("W".to_string()),
last_name: None,
username: Some("WaffleLapkin".to_string()),
},
},
kind: MessageKind::Common {
from: Sender::User(User {
id: 457569668,
is_bot: true,
first_name: "BT".to_string(),
last_name: None,
username: Some("BloodyTestBot".to_string()),
language_code: None,
}),
forward_kind: ForwardKind::Origin {
reply_to_message: None,
},
edit_date: None,
media_kind: MediaKind::Text {
text: "text".to_string(),
entities: vec![],
},
reply_markup: None,
},
}
}
fn message_update() -> Update {
Update {
id: 0,
kind: UpdateKind::Message(message()),
}
}
fn one_message_updater(
) -> StreamUpdater<impl Stream<Item = Result<Update, Infallible>>> {
use futures::{future::ready, stream};
StreamUpdater::new(stream::once(ready(Ok(message_update()))))
}
}

View file

@ -0,0 +1,3 @@
pub use filter::FilterDispatcher;
pub mod filter;

View file

@ -0,0 +1,110 @@
use crate::{dispatching::Filter, types::Message};
pub struct CommandFilter {
command: String,
}
impl Filter<Message> for CommandFilter {
fn test(&self, value: &Message) -> bool {
match value.text() {
Some(text) => match text.split_whitespace().next() {
Some(command) => self.command == command,
None => false,
},
None => false,
}
}
}
impl CommandFilter {
pub fn new<T>(command: T) -> Self
where
T: Into<String>,
{
Self {
command: '/'.to_string() + &command.into(),
}
}
pub fn with_prefix<T>(command: T, prefix: T) -> Self
where
T: Into<String>,
{
Self {
command: prefix.into() + &command.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{
Chat, ChatKind, ForwardKind, MediaKind, MessageKind, Sender, User,
};
#[test]
fn commands_are_equal() {
let filter = CommandFilter::new("command".to_string());
let message = create_message_with_text("/command".to_string());
assert!(filter.test(&message));
}
#[test]
fn commands_are_not_equal() {
let filter = CommandFilter::new("command".to_string());
let message =
create_message_with_text("/not_equal_command".to_string());
assert_eq!(filter.test(&message), false);
}
#[test]
fn command_have_args() {
let filter = CommandFilter::new("command".to_string());
let message =
create_message_with_text("/command arg1 arg2".to_string());
assert!(filter.test(&message));
}
#[test]
fn message_have_only_whitespace() {
let filter = CommandFilter::new("command".to_string());
let message = create_message_with_text(" ".to_string());
assert_eq!(filter.test(&message), false);
}
fn create_message_with_text(text: String) -> Message {
Message {
id: 0,
date: 0,
chat: Chat {
id: 0,
kind: ChatKind::Private {
type_: (),
username: None,
first_name: None,
last_name: None,
},
photo: None,
},
kind: MessageKind::Common {
from: Sender::User(User {
id: 0,
is_bot: false,
first_name: "".to_string(),
last_name: None,
username: None,
language_code: None,
}),
forward_kind: ForwardKind::Origin {
reply_to_message: None,
},
edit_date: None,
media_kind: MediaKind::Text {
text,
entities: vec![],
},
reply_markup: None,
},
}
}
}

View file

@ -6,7 +6,7 @@ pub trait Filter<T> {
}
/// ```
/// use async_telegram_bot::dispatcher::filter::Filter;
/// use telebofr::dispatching::filters::Filter;
///
/// let closure = |i: &i32| -> bool { *i >= 42 };
/// assert!(closure.test(&42));
@ -22,13 +22,15 @@ impl<T, F: Fn(&T) -> bool> Filter<T> for F {
}
/// ```
/// use async_telegram_bot::dispatcher::filter::Filter;
/// use telebofr::dispatching::filters::Filter;
///
/// assert!(true.test(&()));
/// assert_eq!(false.test(&()), false);
/// ```
impl<T> Filter<T> for bool {
fn test(&self, _: &T) -> bool { *self }
fn test(&self, _: &T) -> bool {
*self
}
}
/// And filter.
@ -40,7 +42,7 @@ impl<T> Filter<T> for bool {
///
/// ## Examples
/// ```
/// use async_telegram_bot::dispatcher::filter::{And, Filter};
/// use telebofr::dispatching::filters::{And, Filter};
///
/// // Note: bool can be treated as `Filter` that always return self.
/// assert_eq!(And::new(true, false).test(&()), false);
@ -71,18 +73,17 @@ where
///
/// ## Examples
/// ```
/// use async_telegram_bot::dispatcher::filter::{and, Filter};
/// use telebofr::dispatching::filters::{and, Filter};
///
/// assert!(and(true, true).test(&()));
/// assert_eq!(and(true, false).test(&()), false);
/// ```
///
/// [`And::new`]: crate::dispatcher::filter::And::new
/// [`And::new`]: crate::dispatching::filter::And::new
pub fn and<A, B>(a: A, b: B) -> And<A, B> {
And::new(a, b)
}
/// Or filter.
///
/// Passes if at least one underlying filters passes.
@ -92,7 +93,7 @@ pub fn and<A, B>(a: A, b: B) -> And<A, B> {
///
/// ## Examples
/// ```
/// use async_telegram_bot::dispatcher::filter::{Or, Filter};
/// use telebofr::dispatching::filters::{Filter, Or};
///
/// // Note: bool can be treated as `Filter` that always return self.
/// assert!(Or::new(true, false).test(&()));
@ -123,25 +124,24 @@ where
///
/// ## Examples
/// ```
/// use async_telegram_bot::dispatcher::filter::{or, Filter};
/// use telebofr::dispatching::filters::{or, Filter};
///
/// assert!(or(true, false).test(&()));
/// assert_eq!(or(false, false).test(&()), false);
/// ```
///
/// [`Or::new`]: crate::dispatcher::filter::Or::new
/// [`Or::new`]: crate::dispatching::filter::Or::new
pub fn or<A, B>(a: A, b: B) -> Or<A, B> {
Or::new(a, b)
}
/// Not filter.
///
/// Passes if underlying filter don't pass.
///
/// ## Examples
/// ```
/// use async_telegram_bot::dispatcher::filter::{Not, Filter};
/// use telebofr::dispatching::filters::{Filter, Not};
///
/// // Note: bool can be treated as `Filter` that always return self.
/// assert!(Not::new(false).test(&()));
@ -169,13 +169,13 @@ where
///
/// ## Examples
/// ```
/// use async_telegram_bot::dispatcher::filter::{not, Filter};
/// use telebofr::dispatching::filters::{not, Filter};
///
/// assert!(not(false).test(&()));
/// assert_eq!(not(true).test(&()), false);
/// ```
///
/// [`Not::new`]: crate::dispatcher::filter::Not::new
/// [`Not::new`]: crate::dispatching::filter::Not::new
pub fn not<A>(a: A) -> Not<A> {
Not::new(a)
}
@ -187,7 +187,7 @@ pub fn not<A>(a: A) -> Not<A> {
///
/// ## Examples
/// ```
/// use async_telegram_bot::{all, dispatcher::filter::Filter};
/// use telebofr::{all, dispatching::filters::Filter};
///
/// assert!(all![true].test(&()));
/// assert!(all![true, true].test(&()));
@ -199,12 +199,12 @@ pub fn not<A>(a: A) -> Not<A> {
/// assert_eq!(all![false, false].test(&()), false);
/// ```
///
/// [filter]: crate::dispatcher::filter::Filter
/// [filter]: crate::dispatching::filter::Filter
#[macro_export]
macro_rules! all {
($one:expr) => { $one };
($head:expr, $($tail:tt)+) => {
$crate::dispatcher::filter::And::new(
$crate::dispatching::filters::And::new(
$head,
$crate::all!($($tail)+)
)
@ -218,7 +218,7 @@ macro_rules! all {
///
/// ## Examples
/// ```
/// use async_telegram_bot::{any, dispatcher::filter::Filter};
/// use telebofr::{any, dispatching::filters::Filter};
///
/// assert!(any![true].test(&()));
/// assert!(any![true, true].test(&()));
@ -230,24 +230,23 @@ macro_rules! all {
/// assert_eq!(any![false, false, false].test(&()), false);
/// ```
///
/// [filter]: crate::dispatcher::filter::Filter
/// [filter]: crate::dispatching::filter::Filter
#[macro_export]
macro_rules! any {
($one:expr) => { $one };
($head:expr, $($tail:tt)+) => {
$crate::dispatcher::filter::Or::new(
$crate::dispatching::filters::Or::new(
$head,
$crate::all!($($tail)+)
)
};
}
/// Simple wrapper around `Filter` that adds `|` and `&` operators.
///
/// ## Examples
/// ```
/// use async_telegram_bot::dispatcher::filter::{Filter, f, F, And, Or};
/// use telebofr::dispatching::filters::{f, And, Filter, Or, F};
///
/// let flt1 = |i: &i32| -> bool { *i > 17 };
/// let flt2 = |i: &i32| -> bool { *i < 42 };
@ -259,7 +258,6 @@ macro_rules! any {
/// assert_eq!(and.test(&50), false); // `flt2` doesn't pass
/// assert_eq!(and.test(&16), false); // `flt1` doesn't pass
///
///
/// let or = f(flt1) | flt3;
/// assert!(or.test(&19)); // `flt1` passes
/// assert!(or.test(&16)); // `flt2` passes
@ -267,9 +265,8 @@ macro_rules! any {
///
/// assert_eq!(or.test(&17), false); // both don't pass
///
///
/// // Note: only first filter in chain should be wrapped in `f(...)`
/// let complicated: F<Or<And<_, _>, _>>= f(flt1) & flt2 | flt3;
/// let complicated: F<Or<And<_, _>, _>> = f(flt1) & flt2 | flt3;
/// assert!(complicated.test(&2)); // `flt3` passes
/// assert!(complicated.test(&21)); // `flt1` and `flt2` pass
///
@ -280,14 +277,14 @@ pub struct F<A>(A);
/// Constructor fn for [F]
///
/// [F]: crate::dispatcher::filter::F;
/// [F]: crate::dispatching::filter::F;
pub fn f<A>(a: A) -> F<A> {
F(a)
}
impl<T, A> Filter<T> for F<A>
where
A: Filter<T>
A: Filter<T>,
{
fn test(&self, value: &T) -> bool {
self.0.test(value)
@ -310,13 +307,14 @@ impl<A, B> std::ops::BitOr<B> for F<A> {
}
}
/* workaround for `E0207` compiler error */
/// Extensions for filters
pub trait FilterExt<T /* workaround for `E0207` compiler error */> {
pub trait FilterExt<T> {
/// Alias for [`Not::new`]
///
/// ## Examples
/// ```
/// use async_telegram_bot::dispatcher::filter::{Filter, FilterExt};
/// use telebofr::dispatching::filters::{Filter, FilterExt};
///
/// let flt = |i: &i32| -> bool { *i > 0 };
/// let flt = flt.not();
@ -324,8 +322,11 @@ pub trait FilterExt<T /* workaround for `E0207` compiler error */> {
/// assert_eq!(flt.test(&1), false);
/// ```
///
/// [`Not::new`]: crate::dispatcher::filter::Not::new
fn not(self) -> Not<Self> where Self: Sized {
/// [`Not::new`]: crate::dispatching::filter::Not::new
fn not(self) -> Not<Self>
where
Self: Sized,
{
Not::new(self)
}
@ -333,7 +334,7 @@ pub trait FilterExt<T /* workaround for `E0207` compiler error */> {
///
/// ## Examples
/// ```
/// use async_telegram_bot::dispatcher::filter::{Filter, FilterExt};
/// use telebofr::dispatching::filters::{Filter, FilterExt};
///
/// let flt = |i: &i32| -> bool { *i > 0 };
/// let flt = flt.and(|i: &i32| *i < 42);
@ -343,8 +344,11 @@ pub trait FilterExt<T /* workaround for `E0207` compiler error */> {
/// assert_eq!(flt.test(&43), false);
/// ```
///
/// [`Not::new`]: crate::dispatcher::filter::And::new
fn and<B>(self, other: B) -> And<Self, B> where Self: Sized {
/// [`Not::new`]: crate::dispatching::filter::And::new
fn and<B>(self, other: B) -> And<Self, B>
where
Self: Sized,
{
And::new(self, other)
}
@ -352,7 +356,7 @@ pub trait FilterExt<T /* workaround for `E0207` compiler error */> {
///
/// ## Examples
/// ```
/// use async_telegram_bot::dispatcher::filter::{Filter, FilterExt};
/// use telebofr::dispatching::filters::{Filter, FilterExt};
///
/// let flt = |i: &i32| -> bool { *i < 0 };
/// let flt = flt.or(|i: &i32| *i > 42);
@ -362,8 +366,11 @@ pub trait FilterExt<T /* workaround for `E0207` compiler error */> {
/// assert_eq!(flt.test(&17), false);
/// ```
///
/// [`Not::new`]: crate::dispatcher::filter::Or::new
fn or<B>(self, other: B) -> Or<Self, B> where Self: Sized {
/// [`Not::new`]: crate::dispatching::filter::Or::new
fn or<B>(self, other: B) -> Or<Self, B>
where
Self: Sized,
{
Or::new(self, other)
}
}

View file

@ -0,0 +1,100 @@
use crate::{dispatching::Filter, types::Message};
/// Filter which compare caption of media with another text.
/// Returns true if the caption of media is equal to another text, otherwise
/// false.
///
/// NOTE: filter compares only caption of media, does not compare text of
/// message!
///
/// If you want to compare text of message use
/// [MessageTextFilter]
///
/// If you want to compare text and caption use
/// [MessageTextCaptionFilter]
///
/// [MessageTextFilter]: telebofr::dispatching::filters::MessageTextFilter
/// [MessageTextCaptionFilter]:
/// telebofr::dispatching::filters::MessageTextCaptionFilter
pub struct MessageCaptionFilter {
text: String,
}
impl Filter<Message> for MessageCaptionFilter {
fn test(&self, value: &Message) -> bool {
match value.caption() {
Some(caption) => self.text == caption,
None => false,
}
}
}
impl MessageCaptionFilter {
pub fn new<T>(text: T) -> Self
where
T: Into<String>,
{
Self { text: text.into() }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{
Chat, ChatKind, ForwardKind, MediaKind, MessageKind, Sender, User,
};
#[test]
fn captions_are_equal() {
let filter = MessageCaptionFilter::new("caption".to_string());
let message = create_message_with_caption("caption".to_string());
assert!(filter.test(&message));
}
#[test]
fn captions_are_not_equal() {
let filter = MessageCaptionFilter::new("caption".to_string());
let message =
create_message_with_caption("not equal caption".to_string());
assert_eq!(filter.test(&message), false);
}
fn create_message_with_caption(caption: String) -> Message {
Message {
id: 0,
date: 0,
chat: Chat {
id: 0,
kind: ChatKind::Private {
type_: (),
username: None,
first_name: None,
last_name: None,
},
photo: None,
},
kind: MessageKind::Common {
from: Sender::User(User {
id: 0,
is_bot: false,
first_name: "".to_string(),
last_name: None,
username: None,
language_code: None,
}),
forward_kind: ForwardKind::Origin {
reply_to_message: None,
},
edit_date: None,
media_kind: MediaKind::Photo {
photo: vec![],
caption: Some(caption),
caption_entities: vec![],
media_group_id: None,
},
reply_markup: None,
},
}
}
}

View file

@ -0,0 +1,95 @@
use crate::{dispatching::Filter, types::Message};
/// Filter which compare message text with another text.
/// Returns true if the message text is equal to another text, otherwise false.
///
/// NOTE: filter compares only text message, does not compare caption of media!
///
/// If you want to compare caption use
/// [MessageCaptionFilter]
///
/// If you want to compare text and caption use
/// [MessageTextCaptionFilter]
///
/// [MessageCaptionFilter]: telebofr::dispatching::filters::MessageCaptionFilter
/// [MessageTextCaptionFilter]:
/// telebofr::dispatching::filters::MessageTextCaptionFilter
pub struct MessageTextFilter {
text: String,
}
impl Filter<Message> for MessageTextFilter {
fn test(&self, value: &Message) -> bool {
match value.text() {
Some(text) => self.text == text,
None => false,
}
}
}
impl MessageTextFilter {
pub fn new<T>(text: T) -> Self
where
T: Into<String>,
{
Self { text: text.into() }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{
Chat, ChatKind, ForwardKind, MediaKind, MessageKind, Sender, User,
};
#[test]
fn texts_are_equal() {
let filter = MessageTextFilter::new("text");
let message = create_message_with_text("text".to_string());
assert!(filter.test(&message));
}
#[test]
fn texts_are_not_equal() {
let filter = MessageTextFilter::new("text");
let message = create_message_with_text("not equal text".to_string());
assert_eq!(filter.test(&message), false);
}
fn create_message_with_text(text: String) -> Message {
Message {
id: 0,
date: 0,
chat: Chat {
id: 0,
kind: ChatKind::Private {
type_: (),
username: None,
first_name: None,
last_name: None,
},
photo: None,
},
kind: MessageKind::Common {
from: Sender::User(User {
id: 0,
is_bot: false,
first_name: "".to_string(),
last_name: None,
username: None,
language_code: None,
}),
forward_kind: ForwardKind::Origin {
reply_to_message: None,
},
edit_date: None,
media_kind: MediaKind::Text {
text,
entities: vec![],
},
reply_markup: None,
},
}
}
}

View file

@ -0,0 +1,152 @@
use crate::{dispatching::Filter, types::Message};
/// Filter which compare message text or caption of media with another text.
/// Returns true if the message text or caption of media is equal to another
/// text, otherwise false.
///
/// NOTE: filter compares text of message or if it is not exists, compares
/// caption of the message!
///
/// If you want to compare only caption use
/// [MessageCaptionFilter]
///
/// If you want to compare only text use
/// [MessageTextFilter]
///
/// [MessageCaptionFilter]: telebofr::dispatching::filters::MessageCaptionFilter
/// [MessageTextFilter]: telebofr::filter::filters::MessageTextFilter
pub struct MessageTextCaptionFilter {
text: String,
}
impl Filter<Message> for MessageTextCaptionFilter {
fn test(&self, value: &Message) -> bool {
match value.text() {
Some(text) => self.text == text,
None => match value.caption() {
Some(caption) => self.text == caption,
None => false,
},
}
}
}
impl MessageTextCaptionFilter {
pub fn new<T>(text: T) -> Self
where
T: Into<String>,
{
Self { text: text.into() }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{
Chat, ChatKind, ForwardKind, MediaKind, MessageKind, Sender, User,
};
#[test]
fn texts_are_equal() {
let filter = MessageTextCaptionFilter::new("text");
let message = create_message_with_text("text".to_string());
assert!(filter.test(&message));
}
#[test]
fn texts_are_not_equal() {
let filter = MessageTextCaptionFilter::new("text");
let message = create_message_with_text("not equal text".to_string());
assert_eq!(filter.test(&message), false);
}
fn create_message_with_text(text: String) -> Message {
Message {
id: 0,
date: 0,
chat: Chat {
id: 0,
kind: ChatKind::Private {
type_: (),
username: None,
first_name: None,
last_name: None,
},
photo: None,
},
kind: MessageKind::Common {
from: Sender::User(User {
id: 0,
is_bot: false,
first_name: "".to_string(),
last_name: None,
username: None,
language_code: None,
}),
forward_kind: ForwardKind::Origin {
reply_to_message: None,
},
edit_date: None,
media_kind: MediaKind::Text {
text,
entities: vec![],
},
reply_markup: None,
},
}
}
#[test]
fn captions_are_equal() {
let filter = MessageTextCaptionFilter::new("caption".to_string());
let message = create_message_with_caption("caption".to_string());
assert!(filter.test(&message));
}
#[test]
fn captions_are_not_equal() {
let filter = MessageTextCaptionFilter::new("caption".to_string());
let message =
create_message_with_caption("not equal caption".to_string());
assert_eq!(filter.test(&message), false);
}
fn create_message_with_caption(caption: String) -> Message {
Message {
id: 0,
date: 0,
chat: Chat {
id: 0,
kind: ChatKind::Private {
type_: (),
username: None,
first_name: None,
last_name: None,
},
photo: None,
},
kind: MessageKind::Common {
from: Sender::User(User {
id: 0,
is_bot: false,
first_name: "".to_string(),
last_name: None,
username: None,
language_code: None,
}),
forward_kind: ForwardKind::Origin {
reply_to_message: None,
},
edit_date: None,
media_kind: MediaKind::Photo {
photo: vec![],
caption: Some(caption),
caption_entities: vec![],
media_group_id: None,
},
reply_markup: None,
},
}
}
}

View file

@ -0,0 +1,13 @@
pub use main::*;
pub use command::*;
pub use message_caption::*;
pub use message_text::*;
pub use message_text_caption::*;
mod main;
mod command;
mod message_caption;
mod message_text;
mod message_text_caption;

View file

@ -1,12 +1,15 @@
use std::{future::Future, pin::Pin};
use futures::FutureExt;
use std::future::Future;
use std::pin::Pin;
pub type HandlerResult<E> = Result<(), E>;
/// Asynchronous handler for event `T` (like `&self, I -> Future` fn)
pub trait Handler<'a, T, E> {
fn handle(&self, value: T) -> Pin<Box<dyn Future<Output = HandlerResult<E>> + 'a>>;
fn handle(
&self,
value: T,
) -> Pin<Box<dyn Future<Output = HandlerResult<E>> + 'a>>;
}
pub trait IntoHandlerResult<E> {
@ -32,7 +35,10 @@ where
R: IntoHandlerResult<E> + 'a,
E: 'a,
{
fn handle(&self, value: T) -> Pin<Box<dyn Future<Output = HandlerResult<E>> + 'a>> {
fn handle(
&self,
value: T,
) -> Pin<Box<dyn Future<Output = HandlerResult<E>> + 'a>> {
Box::pin(self(value).map(IntoHandlerResult::into_hr))
}
}

15
src/dispatching/mod.rs Normal file
View file

@ -0,0 +1,15 @@
//! Update dispatching.
use async_trait::async_trait;
pub use filters::Filter;
pub use handler::Handler;
pub mod dispatchers;
pub mod filters;
pub mod handler;
pub mod updater;
#[async_trait(? Send)]
pub trait Dispatcher<'a, U> {
async fn dispatch(&'a mut self, updater: U);
}

View file

@ -3,19 +3,17 @@ use std::{
task::{Context, Poll},
};
use pin_project::pin_project;
use futures::{Stream, StreamExt, stream};
use futures::{stream, Stream, StreamExt};
use crate::{
bot::Bot,
types::Update,
RequestError,
};
use pin_project::pin_project;
use crate::{bot::Bot, types::Update, RequestError};
// Currently just a placeholder, but I'll add here some methods
/// Updater is stream of updates.
///
/// Telegram supports 2 ways of [getting updates]: [long polling](Long Polling) and webhook
/// Telegram supports 2 ways of [getting updates]: [long polling](Long Polling)
/// and webhook
///
/// ## Long Polling
///
@ -99,12 +97,16 @@ use crate::{
/// [GetUpdates]: crate::requests::GetUpdates
/// [getting updates]: https://core.telegram.org/bots/api#getting-updates
/// [wiki]: https://en.wikipedia.org/wiki/Push_technology#Long_polling
pub trait Updater<E>: Stream<Item=Result<Update, E>> {}
pub trait Updater:
Stream<Item = Result<Update, <Self as Updater>::Error>>
{
type Error;
}
#[pin_project]
pub struct StreamUpdater<S> {
#[pin]
stream: S
stream: S,
}
impl<S> StreamUpdater<S> {
@ -113,35 +115,49 @@ impl<S> StreamUpdater<S> {
}
}
impl<S, E> Stream for StreamUpdater<S> where S: Stream<Item=Result<Update, E>> {
impl<S, E> Stream for StreamUpdater<S>
where
S: Stream<Item = Result<Update, E>>,
{
type Item = Result<Update, E>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
self.project().stream.poll_next(cx)
}
}
impl<S, E> Updater<E> for StreamUpdater<S> where S: Stream<Item=Result<Update, E>> {}
impl<S, E> Updater for StreamUpdater<S>
where
S: Stream<Item = Result<Update, E>>,
{
type Error = E;
}
pub fn polling<'a>(bot: &'a Bot) -> impl Updater<RequestError> + 'a {
let stream = stream::unfold((bot, 0), |(bot, mut offset)| async move {
// this match converts Result<Vec<_>, _> -> Vec<Result<_, _>>
let updates = match bot.get_updates().offset(offset).send().await {
Ok(updates) => {
if let Some(upd) = updates.last() {
offset = upd.id + 1;
pub fn polling<'a>(bot: &'a Bot) -> impl Updater<Error = RequestError> + 'a {
let stream = stream::unfold((bot, 0), |(bot, mut offset)| {
async move {
// this match converts Result<Vec<_>, _> -> Vec<Result<_, _>>
let updates = match bot.get_updates().offset(offset).send().await {
Ok(updates) => {
if let Some(upd) = updates.last() {
offset = upd.id + 1;
}
updates.into_iter().map(Ok).collect::<Vec<_>>()
}
updates.into_iter().map(|u| Ok(u)).collect::<Vec<_>>()
},
Err(err) => vec![Err(err)]
};
Some((stream::iter(updates), (bot, offset)))
Err(err) => vec![Err(err)],
};
Some((stream::iter(updates), (bot, offset)))
}
})
.flatten();
.flatten();
StreamUpdater { stream }
}
// TODO implement webhook (this actually require webserver and probably we
// should add cargo feature that adds webhook)
//pub fn webhook<'a>(bot: &'a Bot, cfg: WebhookConfig) -> Updater<impl Stream<Item=Result<Update, ???>> + 'a> {}
//pub fn webhook<'a>(bot: &'a cfg: WebhookConfig) -> Updater<impl
// Stream<Item=Result<Update, ???>> + 'a> {}

View file

@ -5,12 +5,13 @@ extern crate serde;
#[macro_use]
extern crate thiserror;
pub use bot::Bot;
pub use errors::{DownloadError, RequestError};
mod errors;
mod network;
pub mod bot;
pub mod dispatcher;
mod bot;
pub mod dispatching;
pub mod requests;
pub mod types;

View file

@ -11,8 +11,8 @@ pub async fn request_multipart<T>(
method_name: &str,
params: Form,
) -> ResponseResult<T>
where
T: DeserializeOwned,
where
T: DeserializeOwned,
{
process_response(
client
@ -30,8 +30,8 @@ pub async fn request_simple<T>(
token: &str,
method_name: &str,
) -> ResponseResult<T>
where
T: DeserializeOwned,
where
T: DeserializeOwned,
{
process_response(
client

View file

@ -1,7 +1,9 @@
use reqwest::StatusCode;
use crate::{
requests::ResponseResult, types::ResponseParameters, RequestError,
requests::ResponseResult,
types::{False, ResponseParameters, True},
RequestError,
};
#[derive(Deserialize)]
@ -10,14 +12,14 @@ pub enum TelegramResponse<R> {
Ok {
/// A dummy field. Used only for deserialization.
#[allow(dead_code)]
ok: bool, // TODO: True type
ok: True,
result: R,
},
Err {
/// A dummy field. Used only for deserialization.
#[allow(dead_code)]
ok: bool, // TODO: False type
ok: False,
description: String,
error_code: u16,

View file

@ -0,0 +1,125 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, ResponseResult},
types::True,
};
/// 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.
#[derive(Debug, Clone, Serialize)]
pub struct AnswerCallbackQuery<'a> {
#[serde(skip_serializing)]
bot: &'a Bot,
/// Unique identifier for the query to be answered.
callback_query_id: String,
/// Text of the notification. If not specified, nothing will be shown to
/// the user, 0-200 characters
#[serde(skip_serializing_if = "Option::is_none")]
text: Option<String>,
/// If true, an alert will be shown by the client instead of a notification
/// at the top of the chat screen. Defaults to false.
#[serde(skip_serializing_if = "Option::is_none")]
show_alert: Option<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.
#[serde(skip_serializing_if = "Option::is_none")]
url: Option<String>,
/// 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.
#[serde(skip_serializing_if = "Option::is_none")]
cache_time: Option<i32>,
}
#[async_trait]
impl Request for AnswerCallbackQuery<'_> {
type Output = True;
async fn send_boxed(self) -> ResponseResult<Self::Output> {
self.send().await
}
}
impl AnswerCallbackQuery<'_> {
pub async fn send(self) -> ResponseResult<True> {
network::request_json(
self.bot.client(),
self.bot.token(),
"answerCallbackQuery",
&self,
)
.await
}
}
impl<'a> AnswerCallbackQuery<'a> {
pub(crate) fn new<S>(bot: &'a Bot, callback_query_id: S) -> Self
where
S: Into<String>,
{
Self {
bot,
callback_query_id: callback_query_id.into(),
text: None,
show_alert: None,
url: None,
cache_time: None,
}
}
pub fn callback_query_id<S>(mut self, value: S) -> Self
where
S: Into<String>,
{
self.callback_query_id = value.into();
self
}
pub fn text<S>(mut self, value: S) -> Self
where
S: Into<String>,
{
self.text = Some(value.into());
self
}
pub fn show_alert<B>(mut self, value: B) -> Self
where
B: Into<bool>,
{
self.show_alert = Some(value.into());
self
}
pub fn url<S>(mut self, value: S) -> Self
where
S: Into<String>,
{
self.url = Some(value.into());
self
}
pub fn cache_time<I>(mut self, value: I) -> Self
where
I: Into<i32>,
{
self.cache_time = Some(value.into());
self
}
}

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::True,
};
@ -16,7 +17,7 @@ use crate::{
/// [`Update`]: crate::types::Update
pub struct AnswerPreCheckoutQuery<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
/// Unique identifier for the query to be answered
pub pre_checkout_query_id: String,
@ -48,8 +49,8 @@ impl Request for AnswerPreCheckoutQuery<'_> {
impl AnswerPreCheckoutQuery<'_> {
pub async fn send(self) -> ResponseResult<True> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"answerPreCheckoutQuery",
&self,
)
@ -59,7 +60,7 @@ impl AnswerPreCheckoutQuery<'_> {
impl<'a> AnswerPreCheckoutQuery<'a> {
pub(crate) fn new<S, B>(
ctx: RequestContext<'a>,
bot: &'a Bot,
pre_checkout_query_id: S,
ok: B,
) -> Self
@ -68,7 +69,7 @@ impl<'a> AnswerPreCheckoutQuery<'a> {
B: Into<bool>,
{
Self {
ctx,
bot,
pre_checkout_query_id: pre_checkout_query_id.into(),
ok: ok.into(),
error_message: None,

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::{ShippingOption, True},
};
@ -15,7 +16,7 @@ use crate::{
/// [`Update`]: crate::types::Update
pub struct AnswerShippingQuery<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
/// Unique identifier for the query to be answered
pub shipping_query_id: String,
@ -49,8 +50,8 @@ impl Request for AnswerShippingQuery<'_> {
impl AnswerShippingQuery<'_> {
pub async fn send(self) -> ResponseResult<True> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"answerShippingQuery",
&self,
)
@ -59,17 +60,13 @@ impl AnswerShippingQuery<'_> {
}
impl<'a> AnswerShippingQuery<'a> {
pub(crate) fn new<S, B>(
ctx: RequestContext<'a>,
shipping_query_id: S,
ok: B,
) -> Self
pub(crate) fn new<S, B>(bot: &'a Bot, shipping_query_id: S, ok: B) -> Self
where
S: Into<String>,
B: Into<bool>,
{
Self {
ctx,
bot,
shipping_query_id: shipping_query_id.into(),
ok: ok.into(),
shipping_options: None,

View file

@ -0,0 +1,73 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, ResponseResult},
types::{ChatId, True},
};
#[derive(Debug, Clone, Serialize)]
pub struct DeleteChatPhoto<'a> {
#[serde(skip_serializing)]
bot: &'a Bot,
chat_id: ChatId,
}
#[async_trait]
impl Request for DeleteChatPhoto<'_> {
type Output = True;
async fn send_boxed(self) -> ResponseResult<Self::Output> {
self.send().await
}
}
impl DeleteChatPhoto<'_> {
async fn send(self) -> ResponseResult<True> {
network::request_json(
self.bot.client(),
self.bot.token(),
"deleteChatPhoto",
&self,
)
.await
}
}
impl<'a> DeleteChatPhoto<'a> {
pub(crate) fn new<C>(bot: &'a Bot, chat_id: C) -> Self
where
C: Into<ChatId>,
{
Self {
bot,
chat_id: chat_id.into(),
}
}
pub fn chat_id<C>(mut self, chat_id: C) -> Self
where
C: Into<ChatId>,
{
self.chat_id = chat_id.into();
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialize() {
let bot = Bot::new("token");
let chat_id = 123;
let method = DeleteChatPhoto::new(&bot, chat_id);
let expected = r#"{"chat_id":123}"#;
let actual = serde_json::to_string::<DeleteChatPhoto>(&method).unwrap();
assert_eq!(actual, expected);
}
}

View file

@ -0,0 +1,64 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, ResponseResult},
types::{ChatId, True},
};
/// 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.
#[derive(Debug, Clone, Serialize)]
pub struct DeleteChatStickerSet<'a> {
#[serde(skip_serializing)]
bot: &'a Bot,
/// Unique identifier for the target chat or username of the target
/// supergroup (in the format @supergroupusername)
chat_id: ChatId,
}
#[async_trait]
impl Request for DeleteChatStickerSet<'_> {
type Output = True;
async fn send_boxed(self) -> ResponseResult<Self::Output> {
self.send().await
}
}
impl DeleteChatStickerSet<'_> {
async fn send(&self) -> ResponseResult<True> {
network::request_json(
self.bot.client(),
self.bot.token(),
"deleteChatStickerSet",
&self,
)
.await
}
}
impl<'a> DeleteChatStickerSet<'a> {
pub(crate) fn new<C>(bot: &'a Bot, chat_id: C) -> Self
where
C: Into<ChatId>,
{
Self {
bot,
chat_id: chat_id.into(),
}
}
pub fn chat_id<C>(mut self, value: C) -> Self
where
C: Into<ChatId>,
{
self.chat_id = value.into();
self
}
}

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::{ChatId, Message, ReplyMarkup},
};
@ -17,7 +18,7 @@ use crate::{
/// [`Message`]: crate::types::Message
pub struct EditMessageLiveLocation<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
#[serde(skip_serializing_if = "Option::is_none")]
/// Required if inline_message_id is not specified. Unique identifier for
@ -53,8 +54,8 @@ impl Request for EditMessageLiveLocation<'_> {
impl EditMessageLiveLocation<'_> {
pub async fn send(self) -> ResponseResult<Message> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"editMessageLiveLocation",
&self,
)
@ -63,17 +64,13 @@ impl EditMessageLiveLocation<'_> {
}
impl<'a> EditMessageLiveLocation<'a> {
pub(crate) fn new<Lt, Lg>(
ctx: RequestContext<'a>,
latitude: Lt,
longitude: Lg,
) -> Self
pub(crate) fn new<Lt, Lg>(bot: &'a Bot, latitude: Lt, longitude: Lg) -> Self
where
Lt: Into<f64>,
Lg: Into<f64>,
{
Self {
ctx,
bot,
chat_id: None,
message_id: None,
inline_message_id: None,

View file

@ -0,0 +1,74 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, ResponseResult},
types::ChatId,
};
#[derive(Debug, Clone, Serialize)]
pub struct ExportCharInviteLink<'a> {
#[serde(skip_serializing)]
bot: &'a Bot,
chat_id: ChatId,
}
#[async_trait]
impl Request for ExportCharInviteLink<'_> {
type Output = String;
async fn send_boxed(self) -> ResponseResult<Self::Output> {
self.send().await
}
}
impl ExportCharInviteLink<'_> {
async fn send(self) -> ResponseResult<String> {
network::request_json(
self.bot.client(),
self.bot.token(),
"exportChatInviteLink",
&self,
)
.await
}
}
impl<'a> ExportCharInviteLink<'a> {
pub(crate) fn new<C>(bot: &'a Bot, chat_id: C) -> Self
where
C: Into<ChatId>,
{
Self {
bot,
chat_id: chat_id.into(),
}
}
pub fn chat_id<C>(mut self, chat_id: C) -> Self
where
C: Into<ChatId>,
{
self.chat_id = chat_id.into();
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialize() {
let bot = Bot::new("token");
let chat_id = 123;
let method = ExportCharInviteLink::new(&bot, chat_id);
let expected = r#"{"chat_id":123}"#;
let actual =
serde_json::to_string::<ExportCharInviteLink>(&method).unwrap();
assert_eq!(actual, expected);
}
}

View file

@ -79,7 +79,10 @@ macro_rules! impl_for_struct {
impl_for_struct!(bool, i32, i64);
impl<T> IntoFormValue for Option<T> where T: IntoFormValue {
impl<T> IntoFormValue for Option<T>
where
T: IntoFormValue,
{
fn into_form_value(self) -> Option<FormValue> {
self.and_then(IntoFormValue::into_form_value)
}
@ -87,8 +90,8 @@ impl<T> IntoFormValue for Option<T> where T: IntoFormValue {
impl IntoFormValue for &[InputMedia] {
fn into_form_value(self) -> Option<FormValue> {
let json = serde_json::to_string(self)
.expect("serde_json::to_string failed");
let json =
serde_json::to_string(self).expect("serde_json::to_string failed");
Some(FormValue::Str(json))
}
}
@ -113,7 +116,7 @@ impl IntoFormValue for ChatId {
fn into_form_value(self) -> Option<FormValue> {
let string = match self {
ChatId::Id(id) => id.to_string(),
ChatId::ChannelUsername(username) => username.clone(),
ChatId::ChannelUsername(username) => username,
};
Some(FormValue::Str(string))
}
@ -121,7 +124,7 @@ impl IntoFormValue for ChatId {
impl IntoFormValue for String {
fn into_form_value(self) -> Option<FormValue> {
Some(FormValue::Str(self.to_owned()))
Some(FormValue::Str(self))
}
}

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::{ChatId, Message},
};
@ -11,7 +12,7 @@ use crate::{
/// [`Message`] is returned.
pub struct ForwardMessage<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
/// Unique identifier for the target chat or username of the target channel
/// (in the format @channelusername)
@ -40,8 +41,8 @@ impl Request for ForwardMessage<'_> {
impl ForwardMessage<'_> {
pub async fn send(self) -> ResponseResult<Message> {
network::request_json(
self.ctx.client,
self.ctx.token,
self.bot.client(),
self.bot.token(),
"forwardMessage",
&self,
)
@ -51,7 +52,7 @@ impl ForwardMessage<'_> {
impl<'a> ForwardMessage<'a> {
pub(crate) fn new<C, Fc, M>(
ctx: RequestContext<'a>,
bot: &'a Bot,
chat_id: C,
from_chat_id: Fc,
message_id: M,
@ -62,7 +63,7 @@ impl<'a> ForwardMessage<'a> {
M: Into<i32>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
from_chat_id: from_chat_id.into(),
message_id: message_id.into(),

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::{Chat, ChatId},
};
@ -13,7 +14,7 @@ use crate::{
#[derive(Debug, Clone, Serialize)]
pub struct GetChat<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
/// Unique identifier for the target chat or username
/// of the target supergroup or channel (in the format @channelusername)
chat_id: ChatId,
@ -31,8 +32,8 @@ impl Request for GetChat<'_> {
impl GetChat<'_> {
pub async fn send(self) -> ResponseResult<Chat> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"getChat",
&self,
)
@ -41,11 +42,21 @@ impl GetChat<'_> {
}
impl<'a> GetChat<'a> {
pub fn chat_id<C>(mut self, value: C) -> Self
pub(crate) fn new<F>(bot: &'a Bot, chat_id: F) -> Self
where
F: Into<ChatId>,
{
Self {
bot,
chat_id: chat_id.into(),
}
}
pub fn chat_id<C>(mut self, chat_id: C) -> Self
where
C: Into<ChatId>,
{
self.chat_id = value.into();
self.chat_id = chat_id.into();
self
}
}

View file

@ -0,0 +1,64 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, ResponseResult},
types::{ChatId, ChatMember},
};
/// 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
#[derive(Debug, Clone, Serialize)]
pub struct GetChatAdministrators<'a> {
#[serde(skip_serializing)]
bot: &'a Bot,
/// Unique identifier for the target chat or username of the target
/// supergroup or channel (in the format @channelusername)
chat_id: ChatId,
}
#[async_trait]
impl Request for GetChatAdministrators<'_> {
type Output = Vec<ChatMember>;
async fn send_boxed(self) -> ResponseResult<Self::Output> {
self.send().await
}
}
impl GetChatAdministrators<'_> {
async fn send(&self) -> ResponseResult<Vec<ChatMember>> {
network::request_json(
self.bot.client(),
self.bot.token(),
"getChatAdministrators",
&self,
)
.await
}
}
impl<'a> GetChatAdministrators<'a> {
pub(crate) fn new<C>(bot: &'a Bot, chat_id: C) -> Self
where
C: Into<ChatId>,
{
Self {
bot,
chat_id: chat_id.into(),
}
}
pub fn chat_id<C>(mut self, value: C) -> Self
where
C: Into<ChatId>,
{
self.chat_id = value.into();
self
}
}

View file

@ -0,0 +1,74 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, ResponseResult},
types::{ChatId, ChatMember},
};
/// Use this method to get information about a member of a chat. Returns a
/// ChatMember object on success.
#[derive(Debug, Clone, Serialize)]
pub struct GetChatMember<'a> {
#[serde(skip_serializing)]
bot: &'a Bot,
/// Unique identifier for the target chat or username of the target
/// supergroup or channel (in the format @channelusername)
chat_id: ChatId,
/// Unique identifier of the target user
user_id: i32,
}
#[async_trait]
impl Request for GetChatMember<'_> {
type Output = ChatMember;
async fn send_boxed(self) -> ResponseResult<Self::Output> {
self.send().await
}
}
impl GetChatMember<'_> {
async fn send(&self) -> ResponseResult<ChatMember> {
network::request_json(
self.bot.client(),
self.bot.token(),
"getChatMember",
&self,
)
.await
}
}
impl<'a> GetChatMember<'a> {
pub(crate) fn new<C, I>(bot: &'a Bot, chat_id: C, user_id: I) -> Self
where
C: Into<ChatId>,
I: Into<i32>,
{
Self {
bot,
chat_id: chat_id.into(),
user_id: user_id.into(),
}
}
pub fn chat_id<C>(mut self, value: C) -> Self
where
C: Into<ChatId>,
{
self.chat_id = value.into();
self
}
pub fn user_id<I>(mut self, value: I) -> Self
where
I: Into<i32>,
{
self.user_id = value.into();
self
}
}

View file

@ -0,0 +1,61 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, ResponseResult},
types::{Chat, ChatId},
};
/// Use this method to get the number of members in a chat. Returns Int on
/// success.
#[derive(Debug, Clone, Serialize)]
pub struct GetChatMembersCount<'a> {
#[serde(skip_serializing)]
bot: &'a Bot,
/// Unique identifier for the target chat or username
/// of the target supergroup or channel (in the format @channelusername)
chat_id: ChatId,
}
#[async_trait]
impl Request for GetChatMembersCount<'_> {
type Output = Chat;
async fn send_boxed(self) -> ResponseResult<Self::Output> {
self.send().await
}
}
impl GetChatMembersCount<'_> {
pub async fn send(self) -> ResponseResult<Chat> {
network::request_json(
self.bot.client(),
self.bot.token(),
"getChatMembersCount",
&self,
)
.await
}
}
impl<'a> GetChatMembersCount<'a> {
pub fn new<C>(bot: &'a Bot, chat_id: C) -> Self
where
C: Into<ChatId>,
{
Self {
bot,
chat_id: chat_id.into(),
}
}
pub fn chat_id<C>(mut self, value: C) -> Self
where
C: Into<ChatId>,
{
self.chat_id = value.into();
self
}
}

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::File,
};
@ -16,7 +17,7 @@ use crate::{
#[derive(Debug, Clone, Serialize)]
pub struct GetFile<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
/// File identifier to get info about
pub file_id: String,
}
@ -33,8 +34,8 @@ impl Request for GetFile<'_> {
impl GetFile<'_> {
pub async fn send(self) -> ResponseResult<File> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"getFile",
&self,
)
@ -43,12 +44,12 @@ impl GetFile<'_> {
}
impl<'a> GetFile<'a> {
pub(crate) fn new<F>(ctx: RequestContext<'a>, value: F) -> Self
pub(crate) fn new<F>(bot: &'a Bot, value: F) -> Self
where
F: Into<String>,
{
Self {
ctx,
bot,
file_id: value.into(),
}
}

View file

@ -1,16 +1,17 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::User,
};
#[derive(Debug, Clone)]
/// A simple method for testing your bot's auth token. Requires no parameters.
/// 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.
pub struct GetMe<'a> {
ctx: RequestContext<'a>,
bot: &'a Bot,
}
#[async_trait]
@ -24,12 +25,13 @@ impl Request for GetMe<'_> {
impl GetMe<'_> {
pub async fn send(self) -> ResponseResult<User> {
network::request_simple(self.ctx.client, self.ctx.token, "getMe").await
network::request_simple(self.bot.client(), self.bot.token(), "getMe")
.await
}
}
impl<'a> GetMe<'a> {
pub(crate) fn new(ctx: RequestContext<'a>) -> Self {
GetMe { ctx }
pub(crate) fn new(bot: &'a Bot) -> Self {
GetMe { bot }
}
}

View file

@ -1,15 +1,16 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::Update,
};
#[derive(Debug, Clone, Serialize)]
pub struct GetUpdates<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
pub offset: Option<i32>,
pub limit: Option<u8>,
@ -41,8 +42,8 @@ impl Request for GetUpdates<'_> {
impl GetUpdates<'_> {
pub async fn send(self) -> ResponseResult<Vec<Update>> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"getUpdates",
&self,
)
@ -51,9 +52,9 @@ impl GetUpdates<'_> {
}
impl<'a> GetUpdates<'a> {
pub(crate) fn new(ctx: RequestContext<'a>) -> Self {
pub(crate) fn new(bot: &'a Bot) -> Self {
Self {
ctx,
bot,
offset: None,
limit: None,
timeout: None,

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::UserProfilePhotos,
};
@ -11,7 +12,7 @@ use crate::{
#[derive(Debug, Clone, Serialize)]
pub struct GetUserProfilePhotos<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
/// Unique identifier of the target user
pub user_id: i32,
/// Sequential number of the first photo to be returned. By default, all
@ -36,8 +37,8 @@ impl Request for GetUserProfilePhotos<'_> {
impl GetUserProfilePhotos<'_> {
async fn send(self) -> ResponseResult<UserProfilePhotos> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"getUserProfilePhotos",
&self,
)
@ -46,12 +47,12 @@ impl GetUserProfilePhotos<'_> {
}
impl<'a> GetUserProfilePhotos<'a> {
pub fn new<U>(ctx: RequestContext<'a>, user_id: U) -> Self
pub fn new<U>(bot: &'a Bot, user_id: U) -> Self
where
U: Into<i32>,
{
Self {
ctx,
bot,
user_id: user_id.into(),
offset: None,
limit: None,

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::{ChatId, True},
};
@ -14,7 +15,7 @@ use crate::{
#[derive(Debug, Clone, Serialize)]
pub struct KickChatMember<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
///Unique identifier for the target group or username of the target
/// supergroup or channel (in the format @channelusername)
pub chat_id: ChatId,
@ -39,8 +40,8 @@ impl Request for KickChatMember<'_> {
impl KickChatMember<'_> {
async fn send(self) -> ResponseResult<True> {
network::request_json(
self.ctx.client,
self.ctx.token,
self.bot.client(),
self.bot.token(),
"kickChatMember",
&self,
)
@ -49,17 +50,13 @@ impl KickChatMember<'_> {
}
impl<'a> KickChatMember<'a> {
pub(crate) fn new<C, U>(
ctx: RequestContext<'a>,
chat_id: C,
user_id: U,
) -> Self
pub(crate) fn new<C, U>(bot: &'a Bot, chat_id: C, user_id: U) -> Self
where
C: Into<ChatId>,
U: Into<i32>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
user_id: user_id.into(),
until_date: None,

View file

@ -0,0 +1,59 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, ResponseResult},
types::ChatId,
};
/// Use this method for your bot to leave a group, supergroup or channel.
/// Returns True on success.
#[derive(Debug, Clone, Serialize)]
pub struct LeaveChat<'a> {
#[serde(skip_serializing)]
bot: &'a Bot,
/// Unique identifier for the target chat or username
/// of the target supergroup or channel (in the format @channelusername)
chat_id: ChatId,
}
#[async_trait]
impl Request for LeaveChat<'_> {
type Output = bool;
async fn send_boxed(self) -> ResponseResult<Self::Output> {
self.send().await
}
}
impl LeaveChat<'_> {
pub async fn send(self) -> ResponseResult<bool> {
network::request_json(
self.bot.client(),
self.bot.token(),
"leaveChat",
&self,
)
.await
}
}
impl<'a> LeaveChat<'a> {
pub(crate) fn new<F>(bot: &'a Bot, chat_id: F) -> Self
where
F: Into<ChatId>,
{
Self {
bot,
chat_id: chat_id.into(),
}
}
pub fn chat_id<C>(mut self, chat_id: C) -> Self
where
C: Into<ChatId>,
{
self.chat_id = chat_id.into();
self
}
}

View file

@ -1,45 +1,70 @@
//! Raw API functions.
//! API requests.
use reqwest::Client;
use serde::de::DeserializeOwned;
use async_trait::async_trait;
use crate::RequestError;
pub use self::{
answer_pre_checkout_query::AnswerPreCheckoutQuery,
answer_shipping_query::AnswerShippingQuery,
edit_message_live_location::EditMessageLiveLocation,
forward_message::ForwardMessage, get_chat::GetChat, get_file::GetFile,
get_me::GetMe, get_updates::GetUpdates,
get_user_profile_photos::GetUserProfilePhotos,
kick_chat_member::KickChatMember, pin_chat_message::PinChatMessage,
promote_chat_member::PromoteChatMember,
restrict_chat_member::RestrictChatMember, send_animation::SendAnimation,
send_audio::SendAudio, send_chat_action::SendChatAction,
send_contact::SendContact, send_document::SendDocument,
send_location::SendLocation, send_media_group::SendMediaGroup,
send_message::SendMessage, send_photo::SendPhoto, send_poll::SendPoll,
send_venue::SendVenue, send_video::SendVideo,
send_video_note::SendVideoNote, send_voice::SendVoice,
stop_message_live_location::StopMessageLiveLocation,
unban_chat_member::UnbanChatMember, unpin_chat_message::UnpinChatMessage,
};
pub use answer_callback_query::*;
pub use answer_pre_checkout_query::*;
pub use answer_shipping_query::*;
pub use delete_chat_photo::*;
pub use delete_chat_sticker_set::*;
pub use edit_message_live_location::*;
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_me::*;
pub use get_updates::*;
pub use get_user_profile_photos::*;
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_document::*;
pub use send_location::*;
pub use send_media_group::*;
pub use send_message::*;
pub use send_photo::*;
pub use send_poll::*;
pub use send_venue::*;
pub use send_video::*;
pub use send_video_note::*;
pub use send_voice::*;
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 stop_message_live_location::*;
pub use unban_chat_member::*;
pub use unpin_chat_message::*;
mod form_builder;
mod utils;
mod answer_callback_query;
mod answer_pre_checkout_query;
mod answer_shipping_query;
mod delete_chat_photo;
mod delete_chat_sticker_set;
mod edit_message_live_location;
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_updates;
mod get_user_profile_photos;
mod kick_chat_member;
mod leave_chat;
mod pin_chat_message;
mod promote_chat_member;
mod restrict_chat_member;
@ -57,12 +82,20 @@ mod send_venue;
mod send_video;
mod send_video_note;
mod send_voice;
mod set_chat_description;
mod set_chat_permissions;
mod set_chat_photo;
mod set_chat_sticker_set;
mod set_chat_title;
mod stop_message_live_location;
mod unban_chat_member;
mod unpin_chat_message;
use async_trait::async_trait;
use serde::de::DeserializeOwned;
/// A type that is returned from `Request::send_boxed`.
pub type ResponseResult<T> = Result<T, RequestError>;
pub type ResponseResult<T> = Result<T, crate::RequestError>;
/// A request that can be sent to Telegram.
#[async_trait]
@ -73,12 +106,3 @@ pub trait Request {
/// Send this request.
async fn send_boxed(self) -> ResponseResult<Self::Output>;
}
/// A context used to send all the requests.
#[derive(Debug, Clone)]
pub struct RequestContext<'a> {
/// An HTTPS client.
pub client: &'a Client,
/// A token of your bot.
pub token: &'a str,
}

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::{ChatId, True},
};
@ -13,7 +14,7 @@ use crate::{
#[derive(Debug, Clone, Serialize)]
pub struct PinChatMessage<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
/// Unique identifier for the target chat or username
/// of the target supergroup or channel (in the format @channelusername)
pub chat_id: ChatId,
@ -22,17 +23,13 @@ pub struct PinChatMessage<'a> {
}
impl<'a> PinChatMessage<'a> {
pub(crate) fn new<C, M>(
ctx: RequestContext<'a>,
chat_id: C,
message_id: M,
) -> Self
pub(crate) fn new<C, M>(bot: &'a Bot, chat_id: C, message_id: M) -> Self
where
C: Into<ChatId>,
M: Into<i32>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
message_id: message_id.into(),
disable_notification: None,
@ -59,8 +56,8 @@ impl Request for PinChatMessage<'_> {
impl PinChatMessage<'_> {
async fn send(self) -> ResponseResult<True> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"pinChatMessage",
&self,
)

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::{ChatId, True},
};
@ -13,7 +14,7 @@ use crate::{
#[derive(Debug, Clone, Serialize)]
pub struct PromoteChatMember<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
///Unique identifier for the target chat or username of the target channel
/// (in the format @channelusername)
pub chat_id: ChatId,
@ -62,8 +63,8 @@ impl Request for PromoteChatMember<'_> {
impl PromoteChatMember<'_> {
pub async fn send(self) -> ResponseResult<True> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"promoteChatMember",
&self,
)
@ -72,17 +73,13 @@ impl PromoteChatMember<'_> {
}
impl<'a> PromoteChatMember<'a> {
pub(crate) fn new<C, U>(
ctx: RequestContext<'a>,
chat_id: C,
user_id: U,
) -> Self
pub(crate) fn new<C, U>(bot: &'a Bot, chat_id: C, user_id: U) -> Self
where
C: Into<ChatId>,
U: Into<i32>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
user_id: user_id.into(),
can_change_info: None,

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::{ChatId, ChatPermissions, True},
};
@ -13,7 +14,7 @@ use crate::{
#[derive(Debug, Clone, Serialize)]
pub struct RestrictChatMember<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
///Unique identifier for the target chat or username of the target
/// supergroup (in the format @supergroupusername)
pub chat_id: ChatId,
@ -40,8 +41,8 @@ impl Request for RestrictChatMember<'_> {
impl RestrictChatMember<'_> {
async fn send(self) -> ResponseResult<True> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"restrictChatMember",
&self,
)
@ -51,7 +52,7 @@ impl RestrictChatMember<'_> {
impl<'a> RestrictChatMember<'a> {
pub(crate) fn new<C, U, P>(
ctx: RequestContext<'a>,
bot: &'a Bot,
chat_id: C,
user_id: U,
permissions: P,
@ -62,7 +63,7 @@ impl<'a> RestrictChatMember<'a> {
P: Into<ChatPermissions>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
user_id: user_id.into(),
permissions: permissions.into(),

View file

@ -1,10 +1,12 @@
use async_trait::async_trait;
use crate::network;
use crate::requests::{Request, RequestContext, ResponseResult};
use crate::types::{ChatId, Message, ParseMode, ReplyMarkup};
use crate::{
bot::Bot,
network,
requests::{Request, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
};
///TODO: add to bot api
///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
@ -12,7 +14,7 @@ use crate::types::{ChatId, Message, ParseMode, ReplyMarkup};
#[derive(Debug, Clone, Serialize)]
pub struct SendAnimation<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
///Unique identifier for the target chat or username of the target channel
/// (in the format @channelusername)
pub chat_id: ChatId,
@ -20,8 +22,7 @@ pub struct SendAnimation<'a> {
/// exists on the Telegram servers (recommended), pass an HTTP URL as a
/// String for Telegram to get an animation from the Internet, or upload a
/// new animation using multipart/form-data. More info on Sending Files »
pub animation: String,
// InputFile or String
pub animation: InputFile,
///Duration of sent animation in seconds
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<u64>,
@ -40,8 +41,7 @@ pub struct SendAnimation<'a> {
/// if the thumbnail was uploaded using multipart/form-data under
/// <file_attach_name> »
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb: Option<String>,
// InputFile or String Optional
pub thumb: Option<InputFile>,
///Animation caption (may also be used when resending animation by
/// file_id), 0-1024 characters
#[serde(skip_serializing_if = "Option::is_none")]
@ -76,8 +76,8 @@ impl Request for SendAnimation<'_> {
impl SendAnimation<'_> {
async fn send(self) -> ResponseResult<Message> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"sendAnimation",
&self,
)
@ -86,17 +86,13 @@ impl SendAnimation<'_> {
}
impl<'a> SendAnimation<'a> {
pub(crate) fn new<C, S>(
ctx: RequestContext<'a>,
chat_id: C,
animation: S,
) -> Self
pub(crate) fn new<C, S>(bot: &'a Bot, chat_id: C, animation: S) -> Self
where
C: Into<ChatId>,
S: Into<String>,
S: Into<InputFile>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
animation: animation.into(),
duration: None,
@ -143,7 +139,7 @@ impl<'a> SendAnimation<'a> {
}
pub fn thumb<T>(mut self, value: T) -> Self
where
T: Into<String>,
T: Into<InputFile>,
{
self.thumb = Some(value.into());
self

View file

@ -1,10 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{
form_builder::FormBuilder, Request, RequestContext, ResponseResult,
},
requests::{form_builder::FormBuilder, Request, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
};
@ -18,7 +17,7 @@ use crate::{
/// [`Message`]: crate::types::Message
/// [`SendVoice`]: crate::requests::SendVoice
pub struct SendAudio<'a> {
ctx: RequestContext<'a>,
bot: &'a Bot,
/// Unique identifier for the target chat or username of the target channel
/// (in the format @channelusername)
@ -86,10 +85,9 @@ impl SendAudio<'_> {
.add("audio", self.audio)
.add("thumb", self.thumb);
network::request_multipart(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"sendAudio",
params.build(),
)
@ -98,17 +96,13 @@ impl SendAudio<'_> {
}
impl<'a> SendAudio<'a> {
pub(crate) fn new<C, A>(
ctx: RequestContext<'a>,
chat_id: C,
audio: A,
) -> Self
pub(crate) fn new<C, A>(bot: &'a Bot, chat_id: C, audio: A) -> Self
where
C: Into<ChatId>,
A: Into<InputFile>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
audio: audio.into(),
caption: None,

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::{ChatAction, ChatId, True},
};
@ -13,7 +14,7 @@ use crate::{
#[derive(Debug, Clone, Serialize)]
pub struct SendChatAction<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
/// Unique identifier for the target chat or
/// username of the target channel (in the format @channelusername)
pub chat_id: ChatId,
@ -37,8 +38,8 @@ impl Request for SendChatAction<'_> {
impl SendChatAction<'_> {
pub async fn send(self) -> ResponseResult<True> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"sendChatAction",
&self,
)
@ -47,17 +48,13 @@ impl SendChatAction<'_> {
}
impl<'a> SendChatAction<'a> {
pub(crate) fn new<Cid, Ca>(
ctx: RequestContext<'a>,
chat_id: Cid,
action: Ca,
) -> Self
pub(crate) fn new<Cid, Ca>(bot: &'a Bot, chat_id: Cid, action: Ca) -> Self
where
Cid: Into<ChatId>,
Ca: Into<ChatAction>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
action: action.into(),
}

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::{ChatId, Message, ReplyMarkup},
};
@ -11,7 +12,7 @@ use crate::{
#[derive(Debug, Clone, Serialize)]
pub struct SendContact<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
/// Unique identifier for the target chat or
/// username of the target channel (in the format @channelusername)
pub chat_id: ChatId,
@ -54,8 +55,8 @@ impl Request for SendContact<'_> {
impl SendContact<'_> {
pub async fn send(self) -> ResponseResult<Message> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"sendContact",
&self,
)
@ -65,7 +66,7 @@ impl SendContact<'_> {
impl<'a> SendContact<'a> {
pub(crate) fn new<C, P, F>(
ctx: RequestContext<'a>,
bot: &'a Bot,
chat_id: C,
phone_number: P,
first_name: F,
@ -76,7 +77,7 @@ impl<'a> SendContact<'a> {
F: Into<String>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
phone_number: phone_number.into(),
first_name: first_name.into(),

View file

@ -1,20 +1,19 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
types::{ChatId, Message, ParseMode, ReplyMarkup},
requests::{Request, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
};
// TODO: add method to bot/api
///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.
#[derive(Debug, Clone, Serialize)]
pub struct SendDocument<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
/// Unique identifier for the target chat or username of the target
/// channel (in the format @channelusername)
pub chat_id: ChatId,
@ -22,8 +21,7 @@ pub struct SendDocument<'a> {
/// 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.»
pub document: String,
//InputFile or String
pub document: 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
@ -33,8 +31,7 @@ pub struct SendDocument<'a> {
/// if the thumbnail was uploaded using multipart/form-data under
/// <file_attach_name>. More info on Sending Files »
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb: Option<String>,
//InputFile or String
pub thumb: Option<InputFile>,
/// Document caption (may also be used when resending documents by
/// file_id), 0-1024 characters
#[serde(skip_serializing_if = "Option::is_none")]
@ -69,8 +66,8 @@ impl Request for SendDocument<'_> {
impl SendDocument<'_> {
pub async fn send(self) -> ResponseResult<Message> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"sendDocument",
&self,
)
@ -79,17 +76,13 @@ impl SendDocument<'_> {
}
impl<'a> SendDocument<'a> {
pub(crate) fn new<C, D>(
ctx: RequestContext<'a>,
chat_id: C,
document: D,
) -> Self
pub(crate) fn new<C, D>(bot: &'a Bot, chat_id: C, document: D) -> Self
where
C: Into<ChatId>,
D: Into<String>,
D: Into<InputFile>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
document: document.into(),
thumb: None,
@ -111,7 +104,7 @@ impl<'a> SendDocument<'a> {
pub fn document<T>(mut self, value: T) -> Self
where
T: Into<String>,
T: Into<InputFile>,
{
self.document = value.into();
self
@ -119,7 +112,7 @@ impl<'a> SendDocument<'a> {
pub fn thumb<T>(mut self, value: T) -> Self
where
T: Into<String>,
T: Into<InputFile>,
{
self.thumb = Some(value.into());
self

View file

@ -3,8 +3,9 @@ use serde::Serialize;
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::{ChatId, Message, ReplyMarkup},
};
@ -13,7 +14,7 @@ use crate::{
/// is returned.
pub struct SendLocation<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
/// Unique identifier for the target chat or username of the target channel
/// (in the format @channelusername)
@ -50,8 +51,8 @@ impl Request for SendLocation<'_> {
impl SendLocation<'_> {
pub async fn send(self) -> ResponseResult<Message> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"sendLocation",
&self,
)
@ -61,7 +62,7 @@ impl SendLocation<'_> {
impl<'a> SendLocation<'a> {
pub(crate) fn new<Lt, Lg, C>(
ctx: RequestContext<'a>,
bot: &'a Bot,
chat_id: C,
latitude: Lt,
longitude: Lg,
@ -72,7 +73,7 @@ impl<'a> SendLocation<'a> {
C: Into<ChatId>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
latitude: latitude.into(),
longitude: longitude.into(),

View file

@ -1,17 +1,16 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network::request_multipart,
requests::{
form_builder::FormBuilder, Request, RequestContext, ResponseResult,
},
types::{ChatId, InputMedia, Message, InputFile},
requests::{form_builder::FormBuilder, Request, ResponseResult},
types::{ChatId, InputFile, InputMedia, Message},
};
/// Use this method to send a group of photos or videos as an album.
#[derive(Debug, Clone)]
pub struct SendMediaGroup<'a> {
ctx: RequestContext<'a>,
bot: &'a Bot,
pub chat_id: ChatId,
pub media: Vec<InputMedia>,
@ -37,17 +36,20 @@ impl SendMediaGroup<'_> {
.add("disable_notification", self.disable_notification)
.add("reply_to_message_id", self.reply_to_message_id);
let form = self.media.into_iter().filter_map(|e| InputFile::from(e).into())
.fold(form, |acc, path: std::path::PathBuf|
acc.add_file(
&path.file_name().unwrap().to_string_lossy().into_owned(),
path,
)
);
let form = self
.media
.into_iter()
.filter_map(|e| InputFile::from(e).into())
.fold(form, |acc, path: std::path::PathBuf| {
acc.add_file(
&path.file_name().unwrap().to_string_lossy().into_owned(),
path,
)
});
request_multipart(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"sendMediaGroup",
form.build(),
)
@ -56,17 +58,13 @@ impl SendMediaGroup<'_> {
}
impl<'a> SendMediaGroup<'a> {
pub(crate) fn new<C, M>(
ctx: RequestContext<'a>,
chat_id: C,
media: M,
) -> Self
pub(crate) fn new<C, M>(bot: &'a Bot, chat_id: C, media: M) -> Self
where
C: Into<ChatId>,
M: Into<Vec<InputMedia>>,
{
SendMediaGroup {
ctx,
bot,
chat_id: chat_id.into(),
media: media.into(),
disable_notification: None,

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::{ChatId, Message, ParseMode, ReplyMarkup},
};
@ -11,7 +12,7 @@ use crate::{
/// returned.
pub struct SendMessage<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
/// Unique identifier for the target chat or username of the target channel
/// (in the format @channelusername)
@ -55,8 +56,8 @@ impl Request for SendMessage<'_> {
impl SendMessage<'_> {
pub async fn send(self) -> ResponseResult<Message> {
network::request_json(
self.ctx.client,
self.ctx.token,
self.bot.client(),
self.bot.token(),
"sendMessage",
&self,
)
@ -65,17 +66,13 @@ impl SendMessage<'_> {
}
impl<'a> SendMessage<'a> {
pub(crate) fn new<C, S>(
ctx: RequestContext<'a>,
chat_id: C,
text: S,
) -> Self
pub(crate) fn new<C, S>(bot: &'a Bot, chat_id: C, text: S) -> Self
where
C: Into<ChatId>,
S: Into<String>,
{
SendMessage {
ctx,
bot,
chat_id: chat_id.into(),
text: text.into(),
parse_mode: None,

View file

@ -1,10 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{
form_builder::FormBuilder, Request, RequestContext, ResponseResult,
},
requests::{form_builder::FormBuilder, Request, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
};
@ -12,7 +11,7 @@ use crate::{
/// Use this method to send photos. On success, the sent [`Message`] is
/// returned.
pub struct SendPhoto<'a> {
ctx: RequestContext<'a>,
bot: &'a Bot,
/// Unique identifier for the target chat or username of the target channel
/// (in the format @channelusername)
@ -64,8 +63,8 @@ impl SendPhoto<'_> {
.add("photo", self.photo);
network::request_multipart(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"sendPhoto",
params.build(),
)
@ -74,17 +73,13 @@ impl SendPhoto<'_> {
}
impl<'a> SendPhoto<'a> {
pub(crate) fn new<C, P>(
ctx: RequestContext<'a>,
chat_id: C,
photo: P,
) -> Self
pub(crate) fn new<C, P>(bot: &'a Bot, chat_id: C, photo: P) -> Self
where
C: Into<ChatId>,
P: Into<InputFile>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
photo: photo.into(),
caption: None,

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::{ChatId, Message, ReplyMarkup},
};
@ -11,7 +12,7 @@ use crate::{
#[derive(Debug, Clone, Serialize)]
pub struct SendPoll<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
/// 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.
@ -44,8 +45,8 @@ impl Request for SendPoll<'_> {
impl SendPoll<'_> {
pub async fn send(self) -> ResponseResult<Message> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"sendPoll",
&self,
)
@ -55,7 +56,7 @@ impl SendPoll<'_> {
impl<'a> SendPoll<'a> {
pub(crate) fn new<C, Q, O>(
ctx: RequestContext<'a>,
bot: &'a Bot,
chat_id: C,
question: Q,
options: O,
@ -66,7 +67,7 @@ impl<'a> SendPoll<'a> {
O: Into<Vec<String>>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
question: question.into(),
options: options.into(),

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::{ChatId, Message, ReplyMarkup},
};
@ -11,7 +12,7 @@ use crate::{
#[derive(Debug, Clone, Serialize)]
pub struct SendVenue<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
/// Unique identifier for the target chat or
/// username of the target channel (in the format @channelusername)
pub chat_id: ChatId,
@ -59,8 +60,8 @@ impl Request for SendVenue<'_> {
impl SendVenue<'_> {
pub async fn send(self) -> ResponseResult<Message> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"sendVenue",
&self,
)
@ -70,7 +71,7 @@ impl SendVenue<'_> {
impl<'a> SendVenue<'a> {
pub(crate) fn new<Lt, Lg, C, T, A>(
ctx: RequestContext<'a>,
bot: &'a Bot,
chat_id: C,
latitude: Lt,
longitude: Lg,
@ -85,7 +86,7 @@ impl<'a> SendVenue<'a> {
A: Into<String>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
latitude: latitude.into(),
longitude: longitude.into(),

View file

@ -1,10 +1,12 @@
use async_trait::async_trait;
use crate::network;
use crate::requests::{Request, RequestContext, ResponseResult};
use crate::types::{ChatId, Message, ParseMode, ReplyMarkup};
use crate::{
bot::Bot,
network,
requests::{Request, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
};
//TODO: add action to bot api
///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
@ -12,7 +14,7 @@ use crate::types::{ChatId, Message, ParseMode, ReplyMarkup};
#[derive(Debug, Clone, Serialize)]
pub struct SendVideo<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
///Unique identifier for the target chat or username of the target channel
/// (in the format @channelusername)
pub chat_id: ChatId,
@ -20,7 +22,7 @@ pub struct SendVideo<'a> {
/// 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 »
pub video: String,
pub video: InputFile,
///Duration of sent video in seconds
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<u64>,
@ -39,8 +41,7 @@ pub struct SendVideo<'a> {
/// if the thumbnail was uploaded using multipart/form-data under
/// <file_attach_name>. More info on Sending Files »
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb: Option<String>,
//InputFile or String
pub thumb: Option<InputFile>,
///Video caption (may also be used when resending videos by file_id),
/// 0-1024 characters
#[serde(skip_serializing_if = "Option::is_none")]
@ -78,8 +79,8 @@ impl Request for SendVideo<'_> {
impl SendVideo<'_> {
async fn send(self) -> ResponseResult<Message> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"sendVideo",
&self,
)
@ -88,17 +89,13 @@ impl SendVideo<'_> {
}
impl<'a> SendVideo<'a> {
pub(crate) fn new<C, V>(
ctx: RequestContext<'a>,
chat_id: C,
video: V,
) -> Self
pub(crate) fn new<C, V>(bot: &'a Bot, chat_id: C, video: V) -> Self
where
C: Into<ChatId>,
V: Into<String>,
V: Into<InputFile>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
video: video.into(),
duration: None,
@ -123,7 +120,7 @@ impl<'a> SendVideo<'a> {
pub fn video<T>(mut self, value: T) -> Self
where
T: Into<String>,
T: Into<InputFile>,
{
self.video = value.into();
self
@ -152,7 +149,7 @@ impl<'a> SendVideo<'a> {
}
pub fn thumb<T>(mut self, value: T) -> Self
where
T: Into<String>,
T: Into<InputFile>,
{
self.thumb = Some(value.into());
self

View file

@ -1,9 +1,10 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
types::{ChatId, Message, ReplyMarkup},
requests::{Request, ResponseResult},
types::{ChatId, InputFile, Message, ReplyMarkup},
};
///As of v.4.0, Telegram clients support rounded square mp4 videos of up to 1
@ -12,7 +13,7 @@ use crate::{
#[derive(Debug, Clone, Serialize)]
pub struct SendVideoNote<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
///Unique identifier for the target chat or username of the target channel
/// (in the format @channelusername)
pub chat_id: ChatId,
@ -20,8 +21,7 @@ pub struct SendVideoNote<'a> {
/// 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
pub video_note: String,
// InputFile or String
pub video_note: InputFile,
///Duration of sent video in seconds
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<u64>,
@ -37,8 +37,7 @@ pub struct SendVideoNote<'a> {
/// if the thumbnail was uploaded using multipart/form-data under
/// <file_attach_name>. More info on Sending Files »
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb: Option<String>,
// InputFile or String
pub thumb: Option<InputFile>,
///Sends the message silently. Users will receive a notification with no
/// sound.
#[serde(skip_serializing_if = "Option::is_none")]
@ -65,8 +64,8 @@ impl Request for SendVideoNote<'_> {
impl SendVideoNote<'_> {
pub async fn send(self) -> ResponseResult<Message> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"sendVideoNote",
&self,
)
@ -75,17 +74,13 @@ impl SendVideoNote<'_> {
}
impl<'a> SendVideoNote<'a> {
pub(crate) fn new<C, V>(
ctx: RequestContext<'a>,
chat_id: C,
video_note: V,
) -> Self
pub(crate) fn new<C, V>(bot: &'a Bot, chat_id: C, video_note: V) -> Self
where
C: Into<ChatId>,
V: Into<String>,
V: Into<InputFile>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
video_note: video_note.into(),
duration: None,
@ -107,7 +102,7 @@ impl<'a> SendVideoNote<'a> {
pub fn video_note<T>(mut self, value: T) -> Self
where
T: Into<String>,
T: Into<InputFile>,
{
self.video_note = value.into();
self
@ -131,7 +126,7 @@ impl<'a> SendVideoNote<'a> {
pub fn thumb<T>(mut self, value: T) -> Self
where
T: Into<String>,
T: Into<InputFile>,
{
self.thumb = Some(value.into());
self

View file

@ -1,9 +1,10 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
types::{ChatId, Message, ParseMode, ReplyMarkup},
requests::{Request, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
};
///Use this method to send audio files, if you want Telegram clients to display
@ -15,7 +16,7 @@ use crate::{
#[derive(Debug, Clone, Serialize)]
pub struct SendVoice<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
/// Unique identifier for the target chat or username of the target channel
/// (in the format @channelusername)
pub chat_id: ChatId,
@ -23,8 +24,7 @@ pub struct SendVoice<'a> {
/// 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 »
pub voice: String,
//InputFile or String
pub voice: InputFile,
/// Voice message caption, 0-1024 characters
#[serde(skip_serializing_if = "Option::is_none")]
pub caption: Option<String>,
@ -62,8 +62,8 @@ impl Request for SendVoice<'_> {
impl SendVoice<'_> {
pub async fn send(self) -> ResponseResult<Message> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"sendVoice",
&self,
)
@ -72,17 +72,13 @@ impl SendVoice<'_> {
}
impl<'a> SendVoice<'a> {
pub(crate) fn new<C, V>(
ctx: RequestContext<'a>,
chat_id: C,
voice: V,
) -> Self
pub(crate) fn new<C, V>(bot: &'a Bot, chat_id: C, voice: V) -> Self
where
C: Into<ChatId>,
V: Into<String>,
V: Into<InputFile>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
voice: voice.into(),
caption: None,
@ -104,7 +100,7 @@ impl<'a> SendVoice<'a> {
pub fn voice<T>(mut self, value: T) -> Self
where
T: Into<String>,
T: Into<InputFile>,
{
self.voice = value.into();
self

View file

@ -0,0 +1,99 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, ResponseResult},
types::{ChatId, True},
};
#[derive(Debug, Clone, Serialize)]
pub struct SetChatDescription<'a> {
#[serde(skip_serializing)]
bot: &'a Bot,
chat_id: ChatId,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
}
#[async_trait]
impl Request for SetChatDescription<'_> {
type Output = True;
async fn send_boxed(self) -> ResponseResult<Self::Output> {
self.send().await
}
}
impl SetChatDescription<'_> {
pub async fn send(self) -> ResponseResult<True> {
network::request_json(
&self.bot.client(),
&self.bot.token(),
"setChatDescription",
&self,
)
.await
}
}
impl<'a> SetChatDescription<'a> {
pub(crate) fn new<C>(bot: &'a Bot, chat_id: C) -> Self
where
C: Into<ChatId>,
{
Self {
bot,
chat_id: chat_id.into(),
description: None,
}
}
pub fn chat_id<T>(mut self, chat_id: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = chat_id.into();
self
}
pub fn description<T>(mut self, description: T) -> Self
where
T: Into<String>,
{
self.description = Some(description.into());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialize_new() {
let bot = Bot::new("token");
let chat_id = 123;
let method = SetChatDescription::new(&bot, chat_id);
let expected = r#"{"chat_id":123}"#;
let actual =
serde_json::to_string::<SetChatDescription>(&method).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn serialize_description() {
let bot = Bot::new("token");
let chat_id = 123;
let description = "description";
let method =
SetChatDescription::new(&bot, chat_id).description(description);
let expected = r#"{"chat_id":123,"description":"description"}"#;
let actual =
serde_json::to_string::<SetChatDescription>(&method).unwrap();
assert_eq!(actual, expected);
}
}

View file

@ -0,0 +1,96 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, ResponseResult},
types::{ChatId, ChatPermissions, True},
};
#[derive(Debug, Clone, Serialize)]
pub struct SetChatPermissions<'a> {
#[serde(skip_serializing)]
bot: &'a Bot,
chat_id: ChatId,
permissions: ChatPermissions,
}
#[async_trait]
impl Request for SetChatPermissions<'_> {
type Output = True;
async fn send_boxed(self) -> ResponseResult<Self::Output> {
self.send().await
}
}
impl SetChatPermissions<'_> {
async fn send(self) -> ResponseResult<True> {
network::request_json(
self.bot.client(),
self.bot.token(),
"setChatPermissions",
&self,
)
.await
}
}
impl<'a> SetChatPermissions<'a> {
pub(crate) fn new<C, CP>(bot: &'a Bot, chat_id: C, permissions: CP) -> Self
where
C: Into<ChatId>,
CP: Into<ChatPermissions>,
{
Self {
bot,
chat_id: chat_id.into(),
permissions: permissions.into(),
}
}
pub fn chat_id<C>(mut self, chat_id: C) -> Self
where
C: Into<ChatId>,
{
self.chat_id = chat_id.into();
self
}
pub fn permissions<CP>(mut self, permissions: CP) -> Self
where
CP: Into<ChatPermissions>,
{
self.permissions = permissions.into();
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialize() {
let bot = Bot::new("token");
let chat_id = 123;
let permissions = ChatPermissions {
can_send_messages: Some(true),
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,
};
let method = SetChatPermissions::new(&bot, chat_id, permissions);
let expected =
r#"{"chat_id":123,"permissions":{"can_send_messages":true}}"#;
let actual =
serde_json::to_string::<SetChatPermissions>(&method).unwrap();
assert_eq!(actual, expected);
}
}

View file

@ -0,0 +1,90 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{form_builder::FormBuilder, Request, ResponseResult},
types::{ChatId, InputFile, True},
};
#[derive(Debug, Clone, Serialize)]
pub struct SetChatPhoto<'a> {
#[serde(skip_serializing)]
bot: &'a Bot,
chat_id: ChatId,
photo: InputFile,
}
#[async_trait]
impl Request for SetChatPhoto<'_> {
type Output = True;
async fn send_boxed(self) -> ResponseResult<Self::Output> {
self.send().await
}
}
impl SetChatPhoto<'_> {
async fn send(self) -> ResponseResult<True> {
let params = FormBuilder::new()
.add("chat_id", self.chat_id)
.add("photo", self.photo);
network::request_multipart(
self.bot.client(),
self.bot.token(),
"setChatPhoto",
params.build(),
)
.await
}
}
impl<'a> SetChatPhoto<'a> {
pub(crate) fn new<C, P>(bot: &'a Bot, chat_id: C, photo: P) -> Self
where
C: Into<ChatId>,
P: Into<InputFile>,
{
Self {
bot,
chat_id: chat_id.into(),
photo: photo.into(),
}
}
pub fn chat_id<C>(mut self, chat_id: C) -> Self
where
C: Into<ChatId>,
{
self.chat_id = chat_id.into();
self
}
pub fn photo<P>(mut self, photo: P) -> Self
where
P: Into<InputFile>,
{
self.photo = photo.into();
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialize() {
let bot = Bot::new("token");
let chat_id = 123;
let photo_url = "https://some_url".to_string();
let method =
SetChatPhoto::new(&bot, chat_id, InputFile::Url(photo_url));
let expected = r#"{"chat_id":123,"photo":"https://some_url"}"#;
let actual = serde_json::to_string::<SetChatPhoto>(&method).unwrap();
assert_eq!(actual, expected);
}
}

View file

@ -0,0 +1,81 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, ResponseResult},
types::{ChatId, True},
};
/// 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, Clone, Serialize)]
pub struct SetChatStickerSet<'a> {
#[serde(skip_serializing)]
bot: &'a Bot,
/// Unique identifier for the target chat or username of the target
/// supergroup (in the format @supergroupusername)
chat_id: ChatId,
/// Name of the sticker set to be set as the group sticker set
sticker_set_name: String,
}
#[async_trait]
impl Request for SetChatStickerSet<'_> {
type Output = True;
async fn send_boxed(self) -> ResponseResult<Self::Output> {
self.send().await
}
}
impl SetChatStickerSet<'_> {
async fn send(&self) -> ResponseResult<True> {
network::request_json(
self.bot.client(),
self.bot.token(),
"setChatStickerSet",
&self,
)
.await
}
}
impl<'a> SetChatStickerSet<'a> {
pub(crate) fn new<C, S>(
bot: &'a Bot,
chat_id: C,
sticker_set_name: S,
) -> Self
where
C: Into<ChatId>,
S: Into<String>,
{
Self {
bot,
chat_id: chat_id.into(),
sticker_set_name: sticker_set_name.into(),
}
}
pub fn chat_id<C>(mut self, value: C) -> Self
where
C: Into<ChatId>,
{
self.chat_id = value.into();
self
}
pub fn sticker_set_name<S>(mut self, value: S) -> Self
where
S: Into<String>,
{
self.sticker_set_name = value.into();
self
}
}

View file

@ -0,0 +1,85 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, ResponseResult},
types::{ChatId, True},
};
#[derive(Debug, Clone, Serialize)]
pub struct SetChatTitle<'a> {
#[serde(skip_serializing)]
bot: &'a Bot,
chat_id: ChatId,
title: String,
}
#[async_trait]
impl Request for SetChatTitle<'_> {
type Output = True;
async fn send_boxed(self) -> ResponseResult<Self::Output> {
self.send().await
}
}
impl SetChatTitle<'_> {
async fn send(self) -> ResponseResult<True> {
network::request_json(
&self.bot.client(),
&self.bot.token(),
"setChatTitle",
&self,
)
.await
}
}
impl<'a> SetChatTitle<'a> {
pub(crate) fn new<C, T>(bot: &'a Bot, chat_id: C, title: T) -> Self
where
C: Into<ChatId>,
T: Into<String>,
{
Self {
bot,
chat_id: chat_id.into(),
title: title.into(),
}
}
pub fn chat_id<C>(mut self, chat_id: C) -> Self
where
C: Into<ChatId>,
{
self.chat_id = chat_id.into();
self
}
pub fn title<C>(mut self, title: C) -> Self
where
C: Into<String>,
{
self.title = title.into();
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialize() {
let bot = Bot::new("token");
let chat_id = 123;
let title = "title";
let method = SetChatTitle::new(&bot, chat_id, title);
let expected = r#"{"chat_id":123,"title":"title"}"#;
let actual = serde_json::to_string::<SetChatTitle>(&method).unwrap();
assert_eq!(actual, expected);
}
}

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::{ChatId, InlineKeyboardMarkup, Message},
};
@ -12,7 +13,7 @@ use crate::{
#[derive(Debug, Clone, Serialize)]
pub struct StopMessageLiveLocation<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
/// Required if inline_message_id is not specified. Unique identifier for
/// the target chat or username of the target channel (in the format
/// @channelusername)
@ -44,8 +45,8 @@ impl Request for StopMessageLiveLocation<'_> {
impl StopMessageLiveLocation<'_> {
pub async fn send(self) -> ResponseResult<Message> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"stopMessageLiveLocation",
&self,
)
@ -54,9 +55,9 @@ impl StopMessageLiveLocation<'_> {
}
impl<'a> StopMessageLiveLocation<'a> {
pub(crate) fn new(ctx: RequestContext<'a>) -> Self {
pub(crate) fn new(bot: &'a Bot) -> Self {
Self {
ctx,
bot,
chat_id: None,
message_id: None,
inline_message_id: None,

View file

@ -1,8 +1,9 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::ChatId,
};
@ -13,7 +14,7 @@ use crate::{
#[derive(Debug, Clone, Serialize)]
pub struct UnbanChatMember<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
bot: &'a Bot,
///Unique identifier for the target group or username of the target
/// supergroup or channel (in the format @channelusername)
pub chat_id: ChatId,
@ -33,8 +34,8 @@ impl Request for UnbanChatMember<'_> {
impl UnbanChatMember<'_> {
pub async fn send(self) -> ResponseResult<bool> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"unbanChatMember",
&self,
)
@ -43,17 +44,13 @@ impl UnbanChatMember<'_> {
}
impl<'a> UnbanChatMember<'a> {
pub(crate) fn new<C, U>(
ctx: RequestContext<'a>,
chat_id: C,
user_id: U,
) -> Self
pub(crate) fn new<C, U>(bot: &'a Bot, chat_id: C, user_id: U) -> Self
where
C: Into<ChatId>,
U: Into<i32>,
{
Self {
ctx,
bot,
chat_id: chat_id.into(),
user_id: user_id.into(),
}

View file

@ -1,15 +1,16 @@
use async_trait::async_trait;
use crate::{
bot::Bot,
network,
requests::{Request, RequestContext, ResponseResult},
requests::{Request, ResponseResult},
types::{ChatId, True},
};
#[derive(Debug, Clone, Serialize)]
pub struct UnpinChatMessage<'a> {
#[serde(skip_serializing)]
pub ctx: RequestContext<'a>,
bot: &'a Bot,
pub chat_id: ChatId,
}
@ -26,8 +27,8 @@ impl Request for UnpinChatMessage<'_> {
impl UnpinChatMessage<'_> {
pub async fn send(self) -> ResponseResult<True> {
network::request_json(
&self.ctx.client,
&self.ctx.token,
self.bot.client(),
self.bot.token(),
"unpinChatMessage",
&self,
)
@ -36,12 +37,12 @@ impl UnpinChatMessage<'_> {
}
impl<'a> UnpinChatMessage<'a> {
pub(crate) fn new<C>(ctx: RequestContext<'a>, value: C) -> Self
pub(crate) fn new<C>(bot: &'a Bot, value: C) -> Self
where
C: Into<ChatId>,
{
Self {
ctx,
bot,
chat_id: value.into(),
}
}

View file

@ -1,11 +1,19 @@
#[derive(Debug, Deserialize, Hash, PartialEq, Eq, Serialize, Clone)]
pub struct ChatPermissions {
#[serde(skip_serializing_if = "Option::is_none")]
pub can_send_messages: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub can_send_media_messages: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub can_send_polls: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub can_send_other_messages: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub can_add_web_page_previews: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub can_change_info: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub can_invite_users: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub can_pin_messages: Option<bool>,
}

View file

@ -1,4 +1,4 @@
use super::passport_file::PassportFile;
use super::PassportFile;
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct EncryptedPassportElement {

View file

@ -45,7 +45,7 @@ pub enum InlineKeyboardButtonKind {
///
/// Example:
/// ```
/// use async_telegram_bot::types::InlineKeyboardButton;
/// use telebofr::types::InlineKeyboardButton;
///
/// let url_button = InlineKeyboardButton::url(
/// "Text".to_string(),

View file

@ -16,9 +16,7 @@ pub struct InlineKeyboardMarkup {
///
/// Example:
/// ```
/// use async_telegram_bot::types::{
/// InlineKeyboardButton, InlineKeyboardMarkup,
/// };
/// use telebofr::types::{InlineKeyboardButton, InlineKeyboardMarkup};
///
/// let url_button = InlineKeyboardButton::url(
/// "text".to_string(),

View file

@ -49,9 +49,8 @@ pub enum InlineQueryResult {
#[cfg(test)]
mod tests {
use crate::types::inline_keyboard_markup::InlineKeyboardMarkup;
use crate::types::parse_mode::ParseMode;
use crate::types::{
inline_keyboard_markup::InlineKeyboardMarkup, parse_mode::ParseMode,
InlineQueryResult, InlineQueryResultCachedAudio, InputMessageContent,
};

View file

@ -1,4 +1,4 @@
/// This object represents one button of the reply keyboard. For simple text
/// 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. Optional fields are mutually exclusive.
#[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Clone)]

View file

@ -1,4 +1,8 @@
use crate::types::{Animation, Audio, Chat, Contact, Document, Game, InlineKeyboardMarkup, Invoice, Location, MessageEntity, PassportData, PhotoSize, Poll, Sticker, SuccessfulPayment, User, Venue, Video, VideoNote, Voice, True};
use crate::types::{
Animation, Audio, Chat, Contact, Document, Game, InlineKeyboardMarkup,
Invoice, Location, MessageEntity, PassportData, PhotoSize, Poll, Sticker,
SuccessfulPayment, True, User, Venue, Video, VideoNote, Voice,
};
#[derive(Debug, Deserialize, PartialEq, Clone)]
pub struct Message {
@ -10,7 +14,6 @@ pub struct Message {
pub kind: MessageKind,
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
#[serde(untagged)]
pub enum MessageKind {
@ -188,23 +191,23 @@ mod getters {
use std::ops::Deref;
use crate::types::{
self, Message, Sender, User, ForwardedFrom, Chat, MessageEntity,
PhotoSize, True,
self,
message::{
MessageKind::{
Common, NewChatMembers, LeftChatMember, NewChatTitle,
NewChatPhoto, DeleteChatPhoto, GroupChatCreated,
ChannelChatCreated, Migrate, Invoice, SuccessfulPayment,
ConnectedWebsite, PassportData
},
ForwardKind::{ChannelForward, NonChannelForward, Origin},
MediaKind::{
Text, Video, Photo, Animation, Audio, Document, Voice, Game,
Sticker, VideoNote, Contact, Location, Poll, Venue
Animation, Audio, Contact, Document, Game, Location, Photo,
Poll, Sticker, Text, Venue, Video, VideoNote, Voice,
},
MessageKind::{
ChannelChatCreated, Common, ConnectedWebsite, DeleteChatPhoto,
GroupChatCreated, Invoice, LeftChatMember, Migrate,
NewChatMembers, NewChatPhoto, NewChatTitle, PassportData,
Pinned, SuccessfulPayment, SupergroupChatCreated,
},
ForwardKind::{NonChannelForward, ChannelForward, Origin}
},
Chat, ForwardedFrom, Message, MessageEntity, PhotoSize, Sender, True,
User,
};
use crate::types::message::MessageKind::{SupergroupChatCreated, Pinned};
/// Getters for [Message] fields from [telegram docs].
///
@ -223,49 +226,67 @@ mod getters {
/// `forward_sender_name`
pub fn forward_from(&self) -> Option<&ForwardedFrom> {
match &self.kind {
Common { forward_kind: NonChannelForward { from, .. }, .. } =>
Some(from),
Common {
forward_kind: NonChannelForward { from, .. },
..
} => Some(from),
_ => None,
}
}
pub fn forward_from_chat(&self) -> Option<&Chat> {
match &self.kind {
Common { forward_kind: ChannelForward { chat, .. }, .. } =>
Some(chat),
Common {
forward_kind: ChannelForward { chat, .. },
..
} => Some(chat),
_ => None,
}
}
pub fn forward_from_message_id(&self) -> Option<&i32> {
match &self.kind {
Common { forward_kind: ChannelForward { message_id, .. }, .. } =>
Some(message_id),
Common {
forward_kind: ChannelForward { message_id, .. },
..
} => Some(message_id),
_ => None,
}
}
pub fn forward_signature(&self) -> Option<&str> {
match &self.kind {
Common { forward_kind: ChannelForward { signature, .. }, .. } =>
signature.as_ref().map(Deref::deref),
Common {
forward_kind: ChannelForward { signature, .. },
..
} => signature.as_ref().map(Deref::deref),
_ => None,
}
}
pub fn forward_date(&self) -> Option<&i32> {
match &self.kind {
Common { forward_kind: ChannelForward { date, .. }, .. } |
Common { forward_kind: NonChannelForward { date, .. }, .. } =>
Some(date),
Common {
forward_kind: ChannelForward { date, .. },
..
}
| Common {
forward_kind: NonChannelForward { date, .. },
..
} => Some(date),
_ => None,
}
}
pub fn reply_to_message(&self) -> Option<&Message> {
match &self.kind {
Common { forward_kind: Origin { reply_to_message, .. }, .. } =>
reply_to_message.as_ref().map(Deref::deref),
Common {
forward_kind:
Origin {
reply_to_message, ..
},
..
} => reply_to_message.as_ref().map(Deref::deref),
_ => None,
}
}
@ -279,104 +300,172 @@ mod getters {
pub fn media_group_id(&self) -> Option<&str> {
match &self.kind {
Common { media_kind: Video { media_group_id, .. }, .. } |
Common { media_kind: Photo { media_group_id, .. }, .. } =>
media_group_id.as_ref().map(Deref::deref),
Common {
media_kind: Video { media_group_id, .. },
..
}
| Common {
media_kind: Photo { media_group_id, .. },
..
} => media_group_id.as_ref().map(Deref::deref),
_ => None,
}
}
pub fn text(&self) -> Option<&str> {
match &self.kind {
Common { media_kind: Text { text, .. }, .. } => Some(text),
Common {
media_kind: Text { text, .. },
..
} => Some(text),
_ => None,
}
}
pub fn entities(&self) -> Option<&[MessageEntity]> {
match &self.kind {
Common { media_kind: Text { entities, .. }, .. } =>
Some(entities),
Common {
media_kind: Text { entities, .. },
..
} => Some(entities),
_ => None,
}
}
pub fn caption_entities(&self) -> Option<&[MessageEntity]> {
match &self.kind {
Common { media_kind: Animation { caption_entities, .. }, .. } |
Common { media_kind: Audio { caption_entities, .. }, .. } |
Common { media_kind: Document { caption_entities, .. }, .. } |
Common { media_kind: Photo { caption_entities, .. }, .. } |
Common { media_kind: Video { caption_entities, .. }, .. } |
Common { media_kind: Voice { caption_entities, .. }, .. } =>
Some(caption_entities),
Common {
media_kind:
Animation {
caption_entities, ..
},
..
}
| Common {
media_kind:
Audio {
caption_entities, ..
},
..
}
| Common {
media_kind:
Document {
caption_entities, ..
},
..
}
| Common {
media_kind:
Photo {
caption_entities, ..
},
..
}
| Common {
media_kind:
Video {
caption_entities, ..
},
..
}
| Common {
media_kind:
Voice {
caption_entities, ..
},
..
} => Some(caption_entities),
_ => None,
}
}
pub fn audio(&self) -> Option<&types::Audio> {
match &self.kind {
Common { media_kind: Audio { audio, .. }, .. } => Some(audio),
Common {
media_kind: Audio { audio, .. },
..
} => Some(audio),
_ => None,
}
}
pub fn document(&self) -> Option<&types::Document> {
match &self.kind {
Common { media_kind: Document { document, .. }, .. } =>
Some(document),
Common {
media_kind: Document { document, .. },
..
} => Some(document),
_ => None,
}
}
pub fn animation(&self) -> Option<&types::Animation> {
match &self.kind {
Common { media_kind: Animation { animation, .. }, .. } =>
Some(animation),
Common {
media_kind: Animation { animation, .. },
..
} => Some(animation),
_ => None,
}
}
pub fn game(&self) -> Option<&types::Game> {
match &self.kind {
Common { media_kind: Game { game, .. }, .. } => Some(game),
Common {
media_kind: Game { game, .. },
..
} => Some(game),
_ => None,
}
}
pub fn photo(&self) -> Option<&[PhotoSize]> {
match &self.kind {
Common { media_kind: Photo { photo, .. }, .. } => Some(photo),
Common {
media_kind: Photo { photo, .. },
..
} => Some(photo),
_ => None,
}
}
pub fn sticker(&self) -> Option<&types::Sticker> {
match &self.kind {
Common { media_kind: Sticker { sticker, .. }, .. } =>
Some(sticker),
Common {
media_kind: Sticker { sticker, .. },
..
} => Some(sticker),
_ => None,
}
}
pub fn video(&self) -> Option<&types::Video> {
match &self.kind {
Common { media_kind: Video { video, .. }, .. } => Some(video),
Common {
media_kind: Video { video, .. },
..
} => Some(video),
_ => None,
}
}
pub fn voice(&self) -> Option<&types::Voice> {
match &self.kind {
Common { media_kind: Voice { voice, .. }, .. } => Some(voice),
Common {
media_kind: Voice { voice, .. },
..
} => Some(voice),
_ => None,
}
}
pub fn video_note(&self) -> Option<&types::VideoNote> {
match &self.kind {
Common { media_kind: VideoNote { video_note, .. }, .. } =>
Some(video_note),
Common {
media_kind: VideoNote { video_note, .. },
..
} => Some(video_note),
_ => None,
}
}
@ -384,12 +473,14 @@ mod getters {
pub fn caption(&self) -> Option<&str> {
match &self.kind {
Common { media_kind, .. } => match media_kind {
Animation { caption, ..} |
Audio { caption, ..} |
Document { caption, ..} |
Photo { caption, ..} |
Video { caption, ..} |
Voice { caption, ..} => caption.as_ref().map(Deref::deref),
Animation { caption, .. }
| Audio { caption, .. }
| Document { caption, .. }
| Photo { caption, .. }
| Video { caption, .. }
| Voice { caption, .. } => {
caption.as_ref().map(Deref::deref)
}
_ => None,
},
_ => None,
@ -398,29 +489,40 @@ mod getters {
pub fn contact(&self) -> Option<&types::Contact> {
match &self.kind {
Common { media_kind: Contact { contact }, .. } => Some(contact),
Common {
media_kind: Contact { contact },
..
} => Some(contact),
_ => None,
}
}
pub fn location(&self) -> Option<&types::Location> {
match &self.kind {
Common { media_kind: Location { location, .. }, .. } =>
Some(location),
Common {
media_kind: Location { location, .. },
..
} => Some(location),
_ => None,
}
}
pub fn venue(&self) -> Option<&types::Venue> {
match &self.kind {
Common { media_kind: Venue { venue, .. }, .. } => Some(venue),
Common {
media_kind: Venue { venue, .. },
..
} => Some(venue),
_ => None,
}
}
pub fn poll(&self) -> Option<&types::Poll> {
match &self.kind {
Common { media_kind: Poll { poll, .. }, .. } => Some(poll),
Common {
media_kind: Poll { poll, .. },
..
} => Some(poll),
_ => None,
}
}
@ -457,47 +559,55 @@ mod getters {
// mb smt like `is_delete_chat_photo(&self) -> bool`?
pub fn delete_chat_photo(&self) -> Option<True> {
match &self.kind {
DeleteChatPhoto { delete_chat_photo } =>
Some(*delete_chat_photo),
DeleteChatPhoto { delete_chat_photo } => {
Some(*delete_chat_photo)
}
_ => None,
}
}
pub fn group_chat_created(&self) -> Option<True> {
match &self.kind {
GroupChatCreated { group_chat_created } =>
Some(*group_chat_created),
GroupChatCreated { group_chat_created } => {
Some(*group_chat_created)
}
_ => None,
}
}
pub fn super_group_chat_created(&self) -> Option<True> {
match &self.kind {
SupergroupChatCreated { supergroup_chat_created } =>
Some(*supergroup_chat_created),
SupergroupChatCreated {
supergroup_chat_created,
} => Some(*supergroup_chat_created),
_ => None,
}
}
pub fn channel_chat_created(&self) -> Option<True> {
match &self.kind {
ChannelChatCreated { channel_chat_created } =>
Some(*channel_chat_created),
ChannelChatCreated {
channel_chat_created,
} => Some(*channel_chat_created),
_ => None,
}
}
pub fn migrate_to_chat_id(&self) -> Option<&i64> {
match &self.kind {
Migrate { migrate_to_chat_id, .. } => Some(migrate_to_chat_id),
Migrate {
migrate_to_chat_id, ..
} => Some(migrate_to_chat_id),
_ => None,
}
}
pub fn migrate_from_chat_id(&self) -> Option<&i64> {
match &self.kind {
Migrate { migrate_from_chat_id, .. } =>
Some(migrate_from_chat_id),
Migrate {
migrate_from_chat_id,
..
} => Some(migrate_from_chat_id),
_ => None,
}
}
@ -516,25 +626,24 @@ mod getters {
}
}
pub fn successful_payment(&self) -> Option<&types::SuccessfulPayment> {
match &self.kind {
SuccessfulPayment { successful_payment } =>
Some(successful_payment),
SuccessfulPayment { successful_payment } => {
Some(successful_payment)
}
_ => None,
}
}
pub fn connected_website(&self) -> Option<&str> {
match &self.kind {
ConnectedWebsite { connected_website } =>
Some(connected_website),
ConnectedWebsite { connected_website } => {
Some(connected_website)
}
_ => None,
}
}
pub fn passport_data(&self) -> Option<&types::PassportData> {
match &self.kind {
PassportData { passport_data } => Some(passport_data),
@ -542,7 +651,6 @@ mod getters {
}
}
pub fn reply_markup(&self) -> Option<&types::InlineKeyboardMarkup> {
match &self.kind {
Common { reply_markup, .. } => reply_markup.as_ref(),
@ -552,7 +660,6 @@ mod getters {
}
}
#[cfg(test)]
mod tests {
use serde_json::from_str;

View file

@ -1,92 +1,87 @@
//! Raw API structures.
//! API types.
pub use self::{
animation::Animation,
audio::Audio,
callback_game::CallbackGame,
callback_query::CallbackQuery,
chat::{Chat, ChatKind, NonPrivateChatKind},
chat_action::ChatAction,
chat_id::ChatId,
chat_member::{ChatMember, ChatMemberStatus},
chat_permissions::ChatPermissions,
chat_photo::ChatPhoto,
chosen_inline_result::ChosenInlineResult,
contact::Contact,
document::Document,
encrypted_credintials::EncryptedCredentials,
encrypted_passport_element::{
EncryptedPassportElement, EncryptedPassportElementKind,
},
file::File,
force_reply::ForceReply,
game::Game,
game_high_score::GameHighScore,
inline_keyboard_button::{InlineKeyboardButton, InlineKeyboardButtonKind},
inline_keyboard_markup::InlineKeyboardMarkup,
inline_query::InlineQuery,
inline_query_result::InlineQueryResult,
inline_query_result_article::InlineQueryResultArticle,
inline_query_result_audio::InlineQueryResultAudio,
inline_query_result_cached_audio::InlineQueryResultCachedAudio,
inline_query_result_cached_document::InlineQueryResultCachedDocument,
inline_query_result_cached_gif::InlineQueryResultCachedGif,
inline_query_result_cached_mpeg4_gif::InlineQueryResultCachedMpeg4Gif,
inline_query_result_cached_photo::InlineQueryResultCachedPhoto,
inline_query_result_cached_sticker::InlineQueryResultCachedSticker,
inline_query_result_cached_video::InlineQueryResultCachedVideo,
inline_query_result_cached_voice::InlineQueryResultCachedVoice,
inline_query_result_contact::InlineQueryResultContact,
inline_query_result_document::InlineQueryResultDocument,
inline_query_result_game::InlineQueryResultGame,
inline_query_result_gif::InlineQueryResultGif,
inline_query_result_location::InlineQueryResultLocation,
inline_query_result_mpeg4_gif::InlineQueryResultMpeg4Gif,
inline_query_result_photo::InlineQueryResultPhoto,
inline_query_result_venue::InlineQueryResultVenue,
inline_query_result_video::InlineQueryResultVideo,
inline_query_result_voice::InlineQueryResultVoice,
input_file::InputFile,
input_media::InputMedia,
input_message_content::InputMessageContent,
invoice::Invoice,
keyboard_button::KeyboardButton,
label_price::LabeledPrice,
location::Location,
login_url::LoginUrl,
mask_position::MaskPosition,
message::{
ForwardKind, ForwardedFrom, MediaKind, Message, MessageKind, Sender,
},
message_entity::MessageEntity,
order_info::OrderInfo,
parse_mode::ParseMode,
passport_data::PassportData,
passport_file::PassportFile,
photo_size::PhotoSize,
poll::{Poll, PollOption},
pre_checkout_query::PreCheckoutQuery,
reply_keyboard_markup::ReplyKeyboardMarkup,
reply_keyboard_remove::ReplyKeyboardRemove,
reply_markup::ReplyMarkup,
response_parameters::ResponseParameters,
send_invoice::SendInvoice,
shipping_address::ShippingAddress,
shipping_option::ShippingOption,
shipping_query::ShippingQuery,
sticker::Sticker,
sticker_set::StickerSet,
successful_payment::SuccessfulPayment,
unit_true::True,
update::{Update, UpdateKind},
user::User,
user_profile_photos::UserProfilePhotos,
venue::Venue,
video::Video,
video_note::VideoNote,
voice::Voice,
webhook_info::WebhookInfo,
};
pub use animation::*;
pub use audio::*;
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 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 label_price::*;
pub use location::*;
pub use login_url::*;
pub use mask_position::*;
pub use message::*;
pub use message_entity::*;
pub use order_info::*;
pub use parse_mode::*;
pub use passport_data::*;
pub use passport_file::*;
pub use photo_size::*;
pub use poll::*;
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 successful_payment::*;
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 animation;
mod audio;
@ -134,6 +129,7 @@ mod shipping_query;
mod sticker;
mod sticker_set;
mod successful_payment;
mod unit_false;
mod unit_true;
mod update;
mod user;
@ -167,7 +163,7 @@ mod inline_query_result_venue;
mod inline_query_result_video;
mod inline_query_result_voice;
mod encrypted_credintials;
mod encrypted_credentials;
mod encrypted_passport_element;
mod passport_data;
mod passport_file;

View file

@ -1,5 +1,4 @@
use super::encrypted_credintials::EncryptedCredentials;
use super::encrypted_passport_element::EncryptedPassportElement;
use super::{EncryptedCredentials, EncryptedPassportElement};
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
pub struct PassportData {

80
src/types/unit_false.rs Normal file
View file

@ -0,0 +1,80 @@
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
pub struct False;
impl std::convert::TryFrom<bool> for False {
type Error = ();
fn try_from(value: bool) -> Result<Self, Self::Error> {
#[allow(clippy::match_bool)]
match value {
true => Err(()),
false => Ok(False),
}
}
}
impl<'de> Deserialize<'de> for False {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, value: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
#[allow(clippy::match_bool)]
match value {
true => Err(E::custom("expected `false`, found `true`")),
false => Ok(False),
}
}
}
impl Serialize for False {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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);
}
}

View file

@ -1,7 +1,9 @@
use serde::de::{self, Deserialize, Deserializer, Visitor};
use serde::ser::{Serialize, Serializer};
use serde::{
de::{self, Deserialize, Deserializer, Visitor},
ser::{Serialize, Serializer},
};
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
pub struct True;
impl std::convert::TryFrom<bool> for True {