mirror of
https://github.com/teloxide/teloxide.git
synced 2025-03-26 00:17:08 +01:00
Merge remote-tracking branch 'master' into api_errors_custom_deserialize
This commit is contained in:
commit
081f75546c
31 changed files with 735 additions and 129 deletions
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
|
@ -3,6 +3,7 @@ on:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
merge_group:
|
||||||
|
|
||||||
name: Continuous integration
|
name: Continuous integration
|
||||||
|
|
||||||
|
@ -88,7 +89,7 @@ jobs:
|
||||||
features: "--features full"
|
features: "--features full"
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
toolchain: nightly-2022-12-23
|
toolchain: nightly-2022-12-23
|
||||||
features: "--all-features"
|
features: "--features full nightly"
|
||||||
- rust: msrv
|
- rust: msrv
|
||||||
toolchain: 1.64.0
|
toolchain: 1.64.0
|
||||||
features: "--features full"
|
features: "--features full"
|
||||||
|
@ -187,7 +188,7 @@ jobs:
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --all-targets --all-features
|
args: --all-targets --features "full nightly"
|
||||||
|
|
||||||
doc:
|
doc:
|
||||||
name: check docs
|
name: check docs
|
||||||
|
|
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -6,13 +6,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## unreleased
|
## unreleased
|
||||||
|
|
||||||
|
## 0.12.2 - 2023-02-15
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `docs.rs` documentation build
|
||||||
|
|
||||||
|
## 0.12.1 - 2023-02-15
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Allow `ChatJoinRequest` updates
|
- Allow `ChatJoinRequest` updates
|
||||||
|
- Some example links in documentation
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- `Update::filter_chat_join_request`
|
- `Update::filter_chat_join_request`
|
||||||
|
- `sqlite-storage-rustls` feature, that allows using sqlite storage without `native-tls`
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated `teloxide-core` to v0.9.1; see its [changelog](https://github.com/teloxide/teloxide/blob/master/crates/teloxide-core/CHANGELOG.md#091---2023-02-15) for more
|
||||||
|
|
||||||
## 0.12.0 - 2023-01-17
|
## 0.12.0 - 2023-01-17
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## unreleased
|
## unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `ChatPermission::can_*` helper functions ([#851][pr851])
|
||||||
|
- `mentioned_users` functions for `CallbackQuery`, `Chat`, `ChatJoinRequest`, `ChatMemberUpdated`, `Game`, `Message`, `Poll`, `Update` which return all contained `User` instances ([#850][pr850])
|
||||||
|
- `Message::video_chat_participants_invited` ([#850][pr850])
|
||||||
|
- `Update::from`, a replacement for `Update::user` ([#850][pr850])
|
||||||
|
|
||||||
|
[pr851]: https://github.com/teloxide/teloxide/pull/851
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
|
||||||
|
- `Update::user`, use `Update::from` instead ([#850][pr850])
|
||||||
|
|
||||||
|
[pr850]: https://github.com/teloxide/teloxide/pull/850
|
||||||
|
|
||||||
|
## 0.9.1 - 2023-02-15
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- `Update::user` now handles channel posts, chat member changes and chat join request updates correctly ([#835][pr835])
|
- `Update::user` now handles channel posts, chat member changes and chat join request updates correctly ([#835][pr835])
|
||||||
|
- In cases when `teloxide` can't deserialize an update, error now includes the full json value ([#826][pr826])
|
||||||
|
- Deserialization of topic messages ([#830][pr830])
|
||||||
|
|
||||||
[pr835]: https://github.com/teloxide/teloxide/pull/835
|
[pr835]: https://github.com/teloxide/teloxide/pull/835
|
||||||
|
[pr826]: https://github.com/teloxide/teloxide/pull/826
|
||||||
|
[pr830]: https://github.com/teloxide/teloxide/pull/830
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `ApiError::ImageProcessFailed` ([#825][pr825])
|
||||||
|
|
||||||
|
[pr825]: https://github.com/teloxide/teloxide/pull/825
|
||||||
|
|
||||||
## 0.9.0 - 2023-01-17
|
## 0.9.0 - 2023-01-17
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "teloxide-core"
|
name = "teloxide-core"
|
||||||
version = "0.9.0"
|
version = "0.9.1"
|
||||||
description = "Core part of the `teloxide` library - telegram bot API client"
|
description = "Core part of the `teloxide` library - telegram bot API client"
|
||||||
|
|
||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
|
|
|
@ -206,7 +206,7 @@ impl Bot {
|
||||||
) -> impl Future<Output = ResponseResult<P::Output>> + 'static
|
) -> impl Future<Output = ResponseResult<P::Output>> + 'static
|
||||||
where
|
where
|
||||||
P: Payload + Serialize,
|
P: Payload + Serialize,
|
||||||
P::Output: DeserializeOwned,
|
P::Output: DeserializeOwned + 'static,
|
||||||
{
|
{
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let token = Arc::clone(&self.token);
|
let token = Arc::clone(&self.token);
|
||||||
|
@ -237,7 +237,7 @@ impl Bot {
|
||||||
) -> impl Future<Output = ResponseResult<P::Output>>
|
) -> impl Future<Output = ResponseResult<P::Output>>
|
||||||
where
|
where
|
||||||
P: MultipartPayload + Serialize,
|
P: MultipartPayload + Serialize,
|
||||||
P::Output: DeserializeOwned,
|
P::Output: DeserializeOwned + 'static,
|
||||||
{
|
{
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let token = Arc::clone(&self.token);
|
let token = Arc::clone(&self.token);
|
||||||
|
@ -267,7 +267,7 @@ impl Bot {
|
||||||
) -> impl Future<Output = ResponseResult<P::Output>>
|
) -> impl Future<Output = ResponseResult<P::Output>>
|
||||||
where
|
where
|
||||||
P: MultipartPayload + Serialize,
|
P: MultipartPayload + Serialize,
|
||||||
P::Output: DeserializeOwned,
|
P::Output: DeserializeOwned + 'static,
|
||||||
{
|
{
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let token = Arc::clone(&self.token);
|
let token = Arc::clone(&self.token);
|
||||||
|
|
|
@ -16,6 +16,7 @@ pub enum RequestError {
|
||||||
/// The group has been migrated to a supergroup with the specified
|
/// The group has been migrated to a supergroup with the specified
|
||||||
/// identifier.
|
/// identifier.
|
||||||
#[error("The group has been migrated to a supergroup with ID #{0}")]
|
#[error("The group has been migrated to a supergroup with ID #{0}")]
|
||||||
|
// FIXME: change to `ChatId` :|
|
||||||
MigrateToChatId(i64),
|
MigrateToChatId(i64),
|
||||||
|
|
||||||
/// In case of exceeding flood control, the number of seconds left to wait
|
/// In case of exceeding flood control, the number of seconds left to wait
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
//! asynchronous and built using [`tokio`].
|
//! asynchronous and built using [`tokio`].
|
||||||
//!
|
//!
|
||||||
//!```toml
|
//!```toml
|
||||||
//! teloxide_core = "0.8"
|
//! teloxide_core = "0.9"
|
||||||
//! ```
|
//! ```
|
||||||
//! _Compiler support: requires rustc 1.64+_.
|
//! _Compiler support: requires rustc 1.64+_.
|
||||||
//!
|
//!
|
||||||
|
@ -117,6 +117,7 @@ mod bot;
|
||||||
|
|
||||||
// implementation details
|
// implementation details
|
||||||
mod serde_multipart;
|
mod serde_multipart;
|
||||||
|
mod util;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod codegen;
|
mod codegen;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::time::Duration;
|
use std::{any::TypeId, time::Duration};
|
||||||
|
|
||||||
use reqwest::{
|
use reqwest::{
|
||||||
header::{HeaderValue, CONTENT_TYPE},
|
header::{HeaderValue, CONTENT_TYPE},
|
||||||
|
@ -19,7 +19,7 @@ pub async fn request_multipart<T>(
|
||||||
_timeout_hint: Option<Duration>,
|
_timeout_hint: Option<Duration>,
|
||||||
) -> ResponseResult<T>
|
) -> ResponseResult<T>
|
||||||
where
|
where
|
||||||
T: DeserializeOwned,
|
T: DeserializeOwned + 'static,
|
||||||
{
|
{
|
||||||
// Workaround for [#460]
|
// Workaround for [#460]
|
||||||
//
|
//
|
||||||
|
@ -58,7 +58,7 @@ pub async fn request_json<T>(
|
||||||
_timeout_hint: Option<Duration>,
|
_timeout_hint: Option<Duration>,
|
||||||
) -> ResponseResult<T>
|
) -> ResponseResult<T>
|
||||||
where
|
where
|
||||||
T: DeserializeOwned,
|
T: DeserializeOwned + 'static,
|
||||||
{
|
{
|
||||||
// Workaround for [#460]
|
// Workaround for [#460]
|
||||||
//
|
//
|
||||||
|
@ -91,7 +91,7 @@ where
|
||||||
|
|
||||||
async fn process_response<T>(response: Response) -> ResponseResult<T>
|
async fn process_response<T>(response: Response) -> ResponseResult<T>
|
||||||
where
|
where
|
||||||
T: DeserializeOwned,
|
T: DeserializeOwned + 'static,
|
||||||
{
|
{
|
||||||
if response.status().is_server_error() {
|
if response.status().is_server_error() {
|
||||||
tokio::time::sleep(DELAY_ON_SERVER_ERROR).await;
|
tokio::time::sleep(DELAY_ON_SERVER_ERROR).await;
|
||||||
|
@ -99,7 +99,176 @@ where
|
||||||
|
|
||||||
let text = response.text().await?;
|
let text = response.text().await?;
|
||||||
|
|
||||||
|
deserialize_response(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_response<T>(text: String) -> Result<T, RequestError>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned + 'static,
|
||||||
|
{
|
||||||
serde_json::from_str::<TelegramResponse<T>>(&text)
|
serde_json::from_str::<TelegramResponse<T>>(&text)
|
||||||
|
.map(|mut response| {
|
||||||
|
use crate::types::{Update, UpdateKind};
|
||||||
|
use std::{any::Any, iter::zip};
|
||||||
|
|
||||||
|
// HACK: Fill-in error information into `UpdateKind::Error`.
|
||||||
|
//
|
||||||
|
// Why? Well, we need `Update` deserialization to be reliable,
|
||||||
|
// even if Telegram breaks something in their Bot API, we want
|
||||||
|
// 1. Deserialization to """succeed"""
|
||||||
|
// 2. Get the `update.id`
|
||||||
|
//
|
||||||
|
// Both of these points are required for `get_updates(...) -> Vec<Update>`
|
||||||
|
// to behave well after Telegram introduces updates that we can't parse.
|
||||||
|
// (1.) makes it so only some of the updates in a butch need to be skipped
|
||||||
|
// (otherwise serde'll stop on the first error). (2.) allows us to issue
|
||||||
|
// the next `get_updates` call with the right offset, even if the last
|
||||||
|
// update in the batch didn't deserialize well.
|
||||||
|
//
|
||||||
|
// serde's interface doesn't allows us to implement `Deserialize` in such
|
||||||
|
// a way, that we could keep the data we couldn't parse, so our
|
||||||
|
// `Deserialize` impl for `UpdateKind` just returns
|
||||||
|
// `UpdateKind::Error(/* some empty-ish value */)`. Here, through some
|
||||||
|
// terrible hacks and downcasting, we fill-in the data we couldn't parse
|
||||||
|
// so that our users can make actionable bug reports.
|
||||||
|
//
|
||||||
|
// We specifically handle `Vec<Update>` here, because that's the return
|
||||||
|
// type of the only method that returns updates.
|
||||||
|
if TypeId::of::<T>() == TypeId::of::<Vec<Update>>() {
|
||||||
|
if let TelegramResponse::Ok { response, .. } = &mut response {
|
||||||
|
if let Some(updates) =
|
||||||
|
(response as &mut T as &mut dyn Any).downcast_mut::<Vec<Update>>()
|
||||||
|
{
|
||||||
|
if updates.iter().any(|u| matches!(u.kind, UpdateKind::Error(_))) {
|
||||||
|
let re_parsed = serde_json::from_str(&text);
|
||||||
|
|
||||||
|
if let Ok(TelegramResponse::Ok { response: values, .. }) = re_parsed {
|
||||||
|
for (update, value) in zip::<_, Vec<_>>(updates, values) {
|
||||||
|
if let UpdateKind::Error(dest) = &mut update.kind {
|
||||||
|
*dest = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response
|
||||||
|
})
|
||||||
.map_err(|source| RequestError::InvalidJson { source, raw: text.into() })?
|
.map_err(|source| RequestError::InvalidJson { source, raw: text.into() })?
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use cool_asserts::assert_matches;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net::request::deserialize_response,
|
||||||
|
types::{True, Update, UpdateKind},
|
||||||
|
ApiError, RequestError,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn smoke_ok() {
|
||||||
|
let json = r#"{"ok":true,"result":true}"#.to_owned();
|
||||||
|
|
||||||
|
let res = deserialize_response::<True>(json);
|
||||||
|
assert_matches!(res, Ok(True));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn smoke_err() {
|
||||||
|
let json =
|
||||||
|
r#"{"ok":false,"description":"Forbidden: bot was blocked by the user"}"#.to_owned();
|
||||||
|
|
||||||
|
let res = deserialize_response::<True>(json);
|
||||||
|
assert_matches!(res, Err(RequestError::Api(ApiError::BotBlocked)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn migrate() {
|
||||||
|
let json = r#"{"ok":false,"description":"this string is ignored","parameters":{"migrate_to_chat_id":123456}}"#.to_owned();
|
||||||
|
|
||||||
|
let res = deserialize_response::<True>(json);
|
||||||
|
assert_matches!(res, Err(RequestError::MigrateToChatId(123456)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn retry_after() {
|
||||||
|
let json = r#"{"ok":false,"description":"this string is ignored","parameters":{"retry_after":123456}}"#.to_owned();
|
||||||
|
|
||||||
|
let res = deserialize_response::<True>(json);
|
||||||
|
assert_matches!(res, Err(RequestError::RetryAfter(duration)) if duration == Duration::from_secs(123456));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_ok() {
|
||||||
|
let json = r#"{
|
||||||
|
"ok":true,
|
||||||
|
"result":[
|
||||||
|
{
|
||||||
|
"update_id":0,
|
||||||
|
"poll_answer":{
|
||||||
|
"poll_id":"POLL_ID",
|
||||||
|
"user": {"id":42,"is_bot":false,"first_name":"blah"},
|
||||||
|
"option_ids": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"#
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
let res = deserialize_response::<Vec<Update>>(json).unwrap();
|
||||||
|
assert_matches!(res, [Update { id: 0, kind: UpdateKind::PollAnswer(_) }]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check that `get_updates` can work with malformed updates.
|
||||||
|
#[test]
|
||||||
|
fn update_err() {
|
||||||
|
let json = r#"{
|
||||||
|
"ok":true,
|
||||||
|
"result":[
|
||||||
|
{
|
||||||
|
"update_id":0,
|
||||||
|
"poll_answer":{
|
||||||
|
"poll_id":"POLL_ID",
|
||||||
|
"user": {"id":42,"is_bot":false,"first_name":"blah"},
|
||||||
|
"option_ids": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id":1,
|
||||||
|
"something unknown to us":17
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id":2,
|
||||||
|
"poll_answer":{
|
||||||
|
"poll_id":"POLL_ID",
|
||||||
|
"user": {"id":42,"is_bot":false,"first_name":"blah"},
|
||||||
|
"option_ids": [3, 4, 8]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id":3,
|
||||||
|
"message":{"some fields are missing":true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"#
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
let res = deserialize_response::<Vec<Update>>(json).unwrap();
|
||||||
|
assert_matches!(
|
||||||
|
res,
|
||||||
|
[
|
||||||
|
Update { id: 0, kind: UpdateKind::PollAnswer(_) },
|
||||||
|
Update { id: 1, kind: UpdateKind::Error(v) } if v.is_object(),
|
||||||
|
Update { id: 2, kind: UpdateKind::PollAnswer(_) },
|
||||||
|
Update { id: 3, kind: UpdateKind::Error(v) } if v.is_object(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -461,6 +461,13 @@ pub(crate) mod serde_rgb {
|
||||||
{
|
{
|
||||||
Ok(from_u32(v))
|
Ok(from_u32(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
self.visit_u32(v.try_into().map_err(|_| E::custom("rgb value doesn't fit u32"))?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
d.deserialize_u32(V)
|
d.deserialize_u32(V)
|
||||||
}
|
}
|
||||||
|
@ -481,5 +488,16 @@ pub(crate) mod serde_rgb {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn json() {}
|
fn json() {
|
||||||
|
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
|
struct Struct {
|
||||||
|
#[serde(with = "self")]
|
||||||
|
color: [u8; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
let json = format!(r#"{{"color":{}}}"#, 0x00AABBCC);
|
||||||
|
let Struct { color } = serde_json::from_str(&json).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(color, [0xAA, 0xBB, 0xCC])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,8 +40,7 @@ pub struct CallbackQuery {
|
||||||
/// [games]: https://core.telegram.org/bots/api#games
|
/// [games]: https://core.telegram.org/bots/api#games
|
||||||
pub chat_instance: String,
|
pub chat_instance: String,
|
||||||
|
|
||||||
/// A data associated with the callback button. Be aware that a bad client
|
/// A data associated with the callback button.
|
||||||
/// can send arbitrary data in this field.
|
|
||||||
pub data: Option<String>,
|
pub data: Option<String>,
|
||||||
|
|
||||||
/// A short name of a Game to be returned, serves as the unique identifier
|
/// A short name of a Game to be returned, serves as the unique identifier
|
||||||
|
@ -49,6 +48,20 @@ pub struct CallbackQuery {
|
||||||
pub game_short_name: Option<String>,
|
pub game_short_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CallbackQuery {
|
||||||
|
/// Returns all users that are "contained" in this `CallbackQuery`
|
||||||
|
/// structure.
|
||||||
|
///
|
||||||
|
/// This might be useful to track information about users.
|
||||||
|
/// Note that this function can return duplicate users.
|
||||||
|
pub fn mentioned_users(&self) -> impl Iterator<Item = &User> {
|
||||||
|
use crate::util::flatten;
|
||||||
|
use std::iter::once;
|
||||||
|
|
||||||
|
once(&self.from).chain(flatten(self.message.as_ref().map(Message::mentioned_users)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::types::UserId;
|
use crate::types::UserId;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::types::{ChatId, ChatLocation, ChatPermissions, ChatPhoto, Message, True};
|
use crate::types::{ChatId, ChatLocation, ChatPermissions, ChatPhoto, Message, True, User};
|
||||||
|
|
||||||
/// This object represents a chat.
|
/// This object represents a chat.
|
||||||
///
|
///
|
||||||
|
@ -493,6 +493,23 @@ impl Chat {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns all users that are "contained" in this `Chat`
|
||||||
|
/// structure.
|
||||||
|
///
|
||||||
|
/// This might be useful to track information about users.
|
||||||
|
///
|
||||||
|
/// Note that this function can return duplicate users.
|
||||||
|
pub fn mentioned_users(&self) -> impl Iterator<Item = &User> {
|
||||||
|
crate::util::flatten(self.pinned_message.as_ref().map(|m| m.mentioned_users()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `{Message, Chat}::mentioned_users` are mutually recursive, as such we
|
||||||
|
/// can't use `->impl Iterator` everywhere, as it would make an infinite
|
||||||
|
/// type. So we need to box somewhere.
|
||||||
|
pub(crate) fn mentioned_users_rec(&self) -> impl Iterator<Item = &User> {
|
||||||
|
crate::util::flatten(self.pinned_message.as_ref().map(|m| m.mentioned_users_rec()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod serde_helper {
|
mod serde_helper {
|
||||||
|
|
|
@ -18,3 +18,15 @@ pub struct ChatJoinRequest {
|
||||||
/// Chat invite link that was used by the user to send the join request
|
/// Chat invite link that was used by the user to send the join request
|
||||||
pub invite_link: Option<ChatInviteLink>,
|
pub invite_link: Option<ChatInviteLink>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ChatJoinRequest {
|
||||||
|
/// Returns all users that are "contained" in this `ChatJoinRequest`
|
||||||
|
/// structure.
|
||||||
|
///
|
||||||
|
/// This might be useful to track information about users.
|
||||||
|
///
|
||||||
|
/// Note that this function can return duplicate users.
|
||||||
|
pub fn mentioned_users(&self) -> impl Iterator<Item = &User> {
|
||||||
|
std::iter::once(&self.from).chain(self.chat.mentioned_users())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,3 +20,21 @@ pub struct ChatMemberUpdated {
|
||||||
/// joining by invite link events only.
|
/// joining by invite link events only.
|
||||||
pub invite_link: Option<ChatInviteLink>,
|
pub invite_link: Option<ChatInviteLink>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ChatMemberUpdated {
|
||||||
|
/// Returns all users that are "contained" in this `ChatMemberUpdated`
|
||||||
|
/// structure.
|
||||||
|
///
|
||||||
|
/// This might be useful to track information about users.
|
||||||
|
///
|
||||||
|
/// Note that this function can return duplicate users.
|
||||||
|
pub fn mentioned_users(&self) -> impl Iterator<Item = &User> {
|
||||||
|
[
|
||||||
|
&self.from,
|
||||||
|
/* ignore `old_chat_member.user`, it should always be the same as the new one */
|
||||||
|
&self.new_chat_member.user,
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.chain(self.chat.mentioned_users())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -84,7 +84,70 @@ bitflags::bitflags! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: add `can_*` methods for convinience
|
impl ChatPermissions {
|
||||||
|
/// Checks for [`SEND_MESSAGES`] permission.
|
||||||
|
///
|
||||||
|
/// [`SEND_MESSAGES`]: ChatPermissions::SEND_MESSAGES
|
||||||
|
pub fn can_send_messages(&self) -> bool {
|
||||||
|
self.contains(ChatPermissions::SEND_MESSAGES)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks for [`SEND_MEDIA_MESSAGES`] permission.
|
||||||
|
///
|
||||||
|
/// [`SEND_MEDIA_MESSAGES`]: ChatPermissions::SEND_MEDIA_MESSAGES
|
||||||
|
pub fn can_send_media_messages(&self) -> bool {
|
||||||
|
self.contains(ChatPermissions::SEND_MEDIA_MESSAGES)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks for [`SEND_POLLS`] permission.
|
||||||
|
///
|
||||||
|
/// [`SEND_POLLS`]: ChatPermissions::SEND_POLLS
|
||||||
|
pub fn can_send_polls(&self) -> bool {
|
||||||
|
self.contains(ChatPermissions::SEND_POLLS)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks for [`SEND_OTHER_MESSAGES`] permission.
|
||||||
|
///
|
||||||
|
/// [`SEND_OTHER_MESSAGES`]: ChatPermissions::SEND_OTHER_MESSAGES
|
||||||
|
pub fn can_send_other_messages(&self) -> bool {
|
||||||
|
self.contains(ChatPermissions::SEND_OTHER_MESSAGES)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks for [`ADD_WEB_PAGE_PREVIEWS`] permission.
|
||||||
|
///
|
||||||
|
/// [`ADD_WEB_PAGE_PREVIEWS`]: ChatPermissions::ADD_WEB_PAGE_PREVIEWS
|
||||||
|
pub fn can_add_web_page_previews(&self) -> bool {
|
||||||
|
self.contains(ChatPermissions::ADD_WEB_PAGE_PREVIEWS)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks for [`CHANGE_INFO`] permission.
|
||||||
|
///
|
||||||
|
/// [`CHANGE_INFO`]: ChatPermissions::CHANGE_INFO
|
||||||
|
pub fn can_change_info(&self) -> bool {
|
||||||
|
self.contains(ChatPermissions::CHANGE_INFO)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks for [`INVITE_USERS`] permission.
|
||||||
|
///
|
||||||
|
/// [`INVITE_USERS`]: ChatPermissions::INVITE_USERS
|
||||||
|
pub fn can_invite_users(&self) -> bool {
|
||||||
|
self.contains(ChatPermissions::INVITE_USERS)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks for [`PIN_MESSAGES`] permission.
|
||||||
|
///
|
||||||
|
/// [`PIN_MESSAGES`]: ChatPermissions::PIN_MESSAGES
|
||||||
|
pub fn can_pin_messages(&self) -> bool {
|
||||||
|
self.contains(ChatPermissions::PIN_MESSAGES)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks for [`MANAGE_TOPICS`] permission.
|
||||||
|
///
|
||||||
|
/// [`MANAGE_TOPICS`]: ChatPermissions::MANAGE_TOPICS
|
||||||
|
pub fn can_manage_topics(&self) -> bool {
|
||||||
|
self.contains(ChatPermissions::MANAGE_TOPICS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper for (de)serialization
|
/// Helper for (de)serialization
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -124,15 +187,15 @@ struct ChatPermissionsRaw {
|
||||||
impl From<ChatPermissions> for ChatPermissionsRaw {
|
impl From<ChatPermissions> for ChatPermissionsRaw {
|
||||||
fn from(this: ChatPermissions) -> Self {
|
fn from(this: ChatPermissions) -> Self {
|
||||||
Self {
|
Self {
|
||||||
can_send_messages: this.contains(ChatPermissions::SEND_MESSAGES),
|
can_send_messages: this.can_send_messages(),
|
||||||
can_send_media_messages: this.contains(ChatPermissions::SEND_MEDIA_MESSAGES),
|
can_send_media_messages: this.can_send_media_messages(),
|
||||||
can_send_polls: this.contains(ChatPermissions::SEND_POLLS),
|
can_send_polls: this.can_send_polls(),
|
||||||
can_send_other_messages: this.contains(ChatPermissions::SEND_OTHER_MESSAGES),
|
can_send_other_messages: this.can_send_other_messages(),
|
||||||
can_add_web_page_previews: this.contains(ChatPermissions::ADD_WEB_PAGE_PREVIEWS),
|
can_add_web_page_previews: this.can_add_web_page_previews(),
|
||||||
can_change_info: this.contains(ChatPermissions::CHANGE_INFO),
|
can_change_info: this.can_change_info(),
|
||||||
can_invite_users: this.contains(ChatPermissions::INVITE_USERS),
|
can_invite_users: this.can_invite_users(),
|
||||||
can_pin_messages: this.contains(ChatPermissions::PIN_MESSAGES),
|
can_pin_messages: this.can_pin_messages(),
|
||||||
can_manage_topics: this.contains(ChatPermissions::MANAGE_TOPICS),
|
can_manage_topics: this.can_manage_topics(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,3 +19,20 @@ pub struct ForumTopicCreated {
|
||||||
// FIXME: CustomEmojiId
|
// FIXME: CustomEmojiId
|
||||||
pub icon_custom_emoji_id: Option<String>,
|
pub icon_custom_emoji_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::types::ForumTopicCreated;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialization() {
|
||||||
|
let json =
|
||||||
|
r#"{"icon_color":9367192,"icon_custom_emoji_id":"5312536423851630001","name":"???"}"#;
|
||||||
|
|
||||||
|
let event = serde_json::from_str::<ForumTopicCreated>(json).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(event.name, "???");
|
||||||
|
assert_eq!(event.icon_color, [0x8E, 0xEE, 0x98]);
|
||||||
|
assert_eq!(event.icon_custom_emoji_id.as_deref(), Some("5312536423851630001"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::types::{Animation, MessageEntity, PhotoSize};
|
use crate::types::{Animation, MessageEntity, PhotoSize, User};
|
||||||
|
|
||||||
/// This object represents a game.
|
/// This object represents a game.
|
||||||
///
|
///
|
||||||
|
@ -39,3 +39,17 @@ pub struct Game {
|
||||||
/// [@Botfather]: https://t.me/botfather
|
/// [@Botfather]: https://t.me/botfather
|
||||||
pub animation: Option<Animation>,
|
pub animation: Option<Animation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Game {
|
||||||
|
/// Returns all users that are "contained" in this `Game`
|
||||||
|
/// structure.
|
||||||
|
///
|
||||||
|
/// This might be useful to track information about users.
|
||||||
|
///
|
||||||
|
/// Note that this function can return duplicate users.
|
||||||
|
pub fn mentioned_users(&self) -> impl Iterator<Item = &User> {
|
||||||
|
use crate::util::{flatten, mentioned_users_from_entities};
|
||||||
|
|
||||||
|
flatten(self.text_entities.as_deref().map(mentioned_users_from_entities))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -123,8 +123,8 @@ impl InputFile {
|
||||||
///
|
///
|
||||||
/// This is used to coordinate with `attach://`.
|
/// This is used to coordinate with `attach://`.
|
||||||
pub(crate) fn id(&self) -> &str {
|
pub(crate) fn id(&self) -> &str {
|
||||||
// FIXME: remove extra alloc
|
let random = || Arc::from(&*uuid::Uuid::new_v4().as_simple().encode_lower(&mut [0; 32]));
|
||||||
self.id.get_or_init(|| uuid::Uuid::new_v4().to_string().into())
|
self.id.get_or_init(random)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if this file needs an attachment i.e. it's not a file_id
|
/// Returns `true` if this file needs an attachment i.e. it's not a file_id
|
||||||
|
|
|
@ -26,6 +26,7 @@ pub struct Message {
|
||||||
/// Unique identifier of a message thread to which the message belongs; for
|
/// Unique identifier of a message thread to which the message belongs; for
|
||||||
/// supergroups only.
|
/// supergroups only.
|
||||||
// FIXME: MessageThreadId or such
|
// FIXME: MessageThreadId or such
|
||||||
|
#[serde(rename = "message_thread_id")]
|
||||||
pub thread_id: Option<i32>,
|
pub thread_id: Option<i32>,
|
||||||
|
|
||||||
/// Date the message was sent in Unix time.
|
/// Date the message was sent in Unix time.
|
||||||
|
@ -116,6 +117,8 @@ pub struct MessageCommon {
|
||||||
pub reply_markup: Option<InlineKeyboardMarkup>,
|
pub reply_markup: Option<InlineKeyboardMarkup>,
|
||||||
|
|
||||||
/// `true`, if the message is sent to a forum topic.
|
/// `true`, if the message is sent to a forum topic.
|
||||||
|
// FIXME: `is_topic_message` is included even in service messages, like ForumTopicCreated.
|
||||||
|
// more this to `Message`
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub is_topic_message: bool,
|
pub is_topic_message: bool,
|
||||||
|
|
||||||
|
@ -612,7 +615,7 @@ mod getters {
|
||||||
MessageGroupChatCreated, MessageInvoice, MessageLeftChatMember, MessageNewChatMembers,
|
MessageGroupChatCreated, MessageInvoice, MessageLeftChatMember, MessageNewChatMembers,
|
||||||
MessageNewChatPhoto, MessageNewChatTitle, MessagePassportData, MessagePinned,
|
MessageNewChatPhoto, MessageNewChatTitle, MessagePassportData, MessagePinned,
|
||||||
MessageProximityAlertTriggered, MessageSuccessfulPayment, MessageSupergroupChatCreated,
|
MessageProximityAlertTriggered, MessageSuccessfulPayment, MessageSupergroupChatCreated,
|
||||||
PhotoSize, True, User,
|
MessageVideoChatParticipantsInvited, PhotoSize, True, User,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Getters for [Message] fields from [telegram docs].
|
/// Getters for [Message] fields from [telegram docs].
|
||||||
|
@ -620,6 +623,7 @@ mod getters {
|
||||||
/// [Message]: crate::types::Message
|
/// [Message]: crate::types::Message
|
||||||
/// [telegram docs]: https://core.telegram.org/bots/api#message
|
/// [telegram docs]: https://core.telegram.org/bots/api#message
|
||||||
impl Message {
|
impl Message {
|
||||||
|
/// Returns the user who sent the message.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn from(&self) -> Option<&User> {
|
pub fn from(&self) -> Option<&User> {
|
||||||
match &self.kind {
|
match &self.kind {
|
||||||
|
@ -1174,6 +1178,18 @@ mod getters {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn video_chat_participants_invited(
|
||||||
|
&self,
|
||||||
|
) -> Option<&types::VideoChatParticipantsInvited> {
|
||||||
|
match &self.kind {
|
||||||
|
VideoChatParticipantsInvited(MessageVideoChatParticipantsInvited {
|
||||||
|
video_chat_participants_invited,
|
||||||
|
}) => Some(video_chat_participants_invited),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn reply_markup(&self) -> Option<&types::InlineKeyboardMarkup> {
|
pub fn reply_markup(&self) -> Option<&types::InlineKeyboardMarkup> {
|
||||||
match &self.kind {
|
match &self.kind {
|
||||||
|
@ -1390,6 +1406,42 @@ impl Message {
|
||||||
pub fn parse_caption_entities(&self) -> Option<Vec<MessageEntityRef<'_>>> {
|
pub fn parse_caption_entities(&self) -> Option<Vec<MessageEntityRef<'_>>> {
|
||||||
self.caption().zip(self.caption_entities()).map(|(t, e)| MessageEntityRef::parse(t, e))
|
self.caption().zip(self.caption_entities()).map(|(t, e)| MessageEntityRef::parse(t, e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns all users that are "contained" in this `Message` structure.
|
||||||
|
///
|
||||||
|
/// This might be useful to track information about users.
|
||||||
|
///
|
||||||
|
/// Note that this function may return quite a few users as it scans
|
||||||
|
/// replies, pinned messages, message entities and more. Also note that this
|
||||||
|
/// function can return duplicate users.
|
||||||
|
pub fn mentioned_users(&self) -> impl Iterator<Item = &User> {
|
||||||
|
use crate::util::{flatten, mentioned_users_from_entities};
|
||||||
|
|
||||||
|
// Lets just hope we didn't forget something here...
|
||||||
|
|
||||||
|
self.from()
|
||||||
|
.into_iter()
|
||||||
|
.chain(self.via_bot.as_ref())
|
||||||
|
.chain(self.chat.mentioned_users_rec())
|
||||||
|
.chain(flatten(self.reply_to_message().map(Self::mentioned_users_rec)))
|
||||||
|
.chain(flatten(self.new_chat_members()))
|
||||||
|
.chain(self.left_chat_member())
|
||||||
|
.chain(self.forward_from_user())
|
||||||
|
.chain(flatten(self.forward_from_chat().map(Chat::mentioned_users_rec)))
|
||||||
|
.chain(flatten(self.game().map(Game::mentioned_users)))
|
||||||
|
.chain(flatten(self.entities().map(mentioned_users_from_entities)))
|
||||||
|
.chain(flatten(self.caption_entities().map(mentioned_users_from_entities)))
|
||||||
|
.chain(flatten(self.poll().map(Poll::mentioned_users)))
|
||||||
|
.chain(flatten(self.proximity_alert_triggered().map(|a| [&a.traveler, &a.watcher])))
|
||||||
|
.chain(flatten(self.video_chat_participants_invited().and_then(|i| i.users.as_deref())))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Message::mentioned_users` is recursive (due to replies), as such we
|
||||||
|
/// can't use `->impl Iterator` everywhere, as it would make an infinite
|
||||||
|
/// type. So we need to box somewhere.
|
||||||
|
pub(crate) fn mentioned_users_rec(&self) -> Box<dyn Iterator<Item = &User> + Send + Sync + '_> {
|
||||||
|
Box::new(self.mentioned_users())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -1836,4 +1888,36 @@ mod tests {
|
||||||
assert!(!entities.is_empty());
|
assert!(!entities.is_empty());
|
||||||
assert_eq!(entities[0].kind().clone(), MessageEntityKind::Url);
|
assert_eq!(entities[0].kind().clone(), MessageEntityKind::Url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn topic_created() {
|
||||||
|
let json = r#"{
|
||||||
|
"chat":{"id":-1001847508954,"is_forum":true,"title":"twest","type":"supergroup"},
|
||||||
|
"date":1675229139,
|
||||||
|
"forum_topic_created":{
|
||||||
|
"icon_color":9367192,
|
||||||
|
"icon_custom_emoji_id":"5312536423851630001",
|
||||||
|
"name":"???"
|
||||||
|
},
|
||||||
|
"from":{
|
||||||
|
"first_name":"вафель'",
|
||||||
|
"id":1253681278,
|
||||||
|
"is_bot":false,
|
||||||
|
"language_code":"en",
|
||||||
|
"username":"wafflelapkin"
|
||||||
|
},
|
||||||
|
"is_topic_message":true,
|
||||||
|
"message_id":4,
|
||||||
|
"message_thread_id":4
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let _: Message = serde_json::from_str(json).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn topic_message() {
|
||||||
|
let json = r#"{"chat":{"id":-1001847508954,"is_forum":true,"title":"twest","type":"supergroup"},"date":1675229140,"from":{"first_name":"вафель'","id":1253681278,"is_bot":false,"language_code":"en","username":"wafflelapkin"},"is_topic_message":true,"message_id":5,"message_thread_id":4,"reply_to_message":{"chat":{"id":-1001847508954,"is_forum":true,"title":"twest","type":"supergroup"},"date":1675229139,"forum_topic_created":{"icon_color":9367192,"icon_custom_emoji_id":"5312536423851630001","name":"???"},"from":{"first_name":"вафель'","id":1253681278,"is_bot":false,"language_code":"en","username":"wafflelapkin"},"is_topic_message":true,"message_id":4,"message_thread_id":4},"text":"blah"}"#;
|
||||||
|
|
||||||
|
let _: Message = serde_json::from_str(json).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::types::{MessageEntity, PollType};
|
use crate::types::{MessageEntity, PollType, User};
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -67,6 +67,20 @@ pub struct PollOption {
|
||||||
pub voter_count: i32,
|
pub voter_count: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Poll {
|
||||||
|
/// Returns all users that are "contained" in this `Poll`
|
||||||
|
/// structure.
|
||||||
|
///
|
||||||
|
/// This might be useful to track information about users.
|
||||||
|
///
|
||||||
|
/// Note that this function can return duplicate users.
|
||||||
|
pub fn mentioned_users(&self) -> impl Iterator<Item = &User> {
|
||||||
|
use crate::util::{flatten, mentioned_users_from_entities};
|
||||||
|
|
||||||
|
flatten(self.explanation_entities.as_deref().map(mentioned_users_from_entities))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -29,7 +29,7 @@ pub struct Update {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Update {
|
impl Update {
|
||||||
// FIXME: rename user => from, add mentioned_users -> impl Iterator<&User>
|
// FIXME: add mentioned_users -> impl Iterator<&User>
|
||||||
|
|
||||||
/// Returns the user that performed the action that caused this update, if
|
/// Returns the user that performed the action that caused this update, if
|
||||||
/// known.
|
/// known.
|
||||||
|
@ -37,7 +37,7 @@ impl Update {
|
||||||
/// This is generally the `from` field (except for `PollAnswer` where it's
|
/// This is generally the `from` field (except for `PollAnswer` where it's
|
||||||
/// `user` and `Poll` with `Error` which don't have such field at all).
|
/// `user` and `Poll` with `Error` which don't have such field at all).
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn user(&self) -> Option<&User> {
|
pub fn from(&self) -> Option<&User> {
|
||||||
use UpdateKind::*;
|
use UpdateKind::*;
|
||||||
|
|
||||||
let from = match &self.kind {
|
let from = match &self.kind {
|
||||||
|
@ -59,6 +59,48 @@ impl Update {
|
||||||
Some(from)
|
Some(from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns all users that are "contained" in this `Update` structure.
|
||||||
|
///
|
||||||
|
/// This might be useful to track information about users.
|
||||||
|
///
|
||||||
|
/// Note that this function may return quite a few users as it scans
|
||||||
|
/// replies, pinned messages, message entities, "via bot" fields and more.
|
||||||
|
/// Also note that this function can return duplicate users.
|
||||||
|
pub fn mentioned_users(&self) -> impl Iterator<Item = &User> {
|
||||||
|
use either::Either::{Left, Right};
|
||||||
|
use std::iter::{empty, once};
|
||||||
|
|
||||||
|
let i0 = Left;
|
||||||
|
let i1 = |x| Right(Left(x));
|
||||||
|
let i2 = |x| Right(Right(Left(x)));
|
||||||
|
let i3 = |x| Right(Right(Right(Left(x))));
|
||||||
|
let i4 = |x| Right(Right(Right(Right(Left(x)))));
|
||||||
|
let i5 = |x| Right(Right(Right(Right(Right(Left(x))))));
|
||||||
|
let i6 = |x| Right(Right(Right(Right(Right(Right(x))))));
|
||||||
|
|
||||||
|
match &self.kind {
|
||||||
|
UpdateKind::Message(message)
|
||||||
|
| UpdateKind::EditedMessage(message)
|
||||||
|
| UpdateKind::ChannelPost(message)
|
||||||
|
| UpdateKind::EditedChannelPost(message) => i0(message.mentioned_users()),
|
||||||
|
|
||||||
|
UpdateKind::InlineQuery(query) => i1(once(&query.from)),
|
||||||
|
UpdateKind::ChosenInlineResult(query) => i1(once(&query.from)),
|
||||||
|
UpdateKind::CallbackQuery(query) => i2(query.mentioned_users()),
|
||||||
|
UpdateKind::ShippingQuery(query) => i1(once(&query.from)),
|
||||||
|
UpdateKind::PreCheckoutQuery(query) => i1(once(&query.from)),
|
||||||
|
UpdateKind::Poll(poll) => i3(poll.mentioned_users()),
|
||||||
|
|
||||||
|
UpdateKind::PollAnswer(answer) => i1(once(&answer.user)),
|
||||||
|
|
||||||
|
UpdateKind::MyChatMember(member) | UpdateKind::ChatMember(member) => {
|
||||||
|
i4(member.mentioned_users())
|
||||||
|
}
|
||||||
|
UpdateKind::ChatJoinRequest(request) => i5(request.mentioned_users()),
|
||||||
|
UpdateKind::Error(_) => i6(empty()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the chat in which is update has happened, if any.
|
/// Returns the chat in which is update has happened, if any.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn chat(&self) -> Option<&Chat> {
|
pub fn chat(&self) -> Option<&Chat> {
|
||||||
|
@ -82,6 +124,11 @@ impl Update {
|
||||||
|
|
||||||
Some(chat)
|
Some(chat)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated(note = "renamed to `from`", since = "0.10.0")]
|
||||||
|
pub fn user(&self) -> Option<&User> {
|
||||||
|
self.from()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
@ -156,7 +203,10 @@ pub enum UpdateKind {
|
||||||
/// An error that happened during deserialization.
|
/// An error that happened during deserialization.
|
||||||
///
|
///
|
||||||
/// This allows `teloxide` to continue working even if telegram adds a new
|
/// This allows `teloxide` to continue working even if telegram adds a new
|
||||||
/// kind of updates.
|
/// kinds of updates.
|
||||||
|
///
|
||||||
|
/// **Note that deserialize implementation always returns an empty value**,
|
||||||
|
/// teloxide fills in the data when doing deserialization.
|
||||||
Error(Value),
|
Error(Value),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,94 +232,63 @@ impl<'de> Deserialize<'de> for UpdateKind {
|
||||||
|
|
||||||
// Try to deserialize a borrowed-str key, or else try deserializing an owned
|
// Try to deserialize a borrowed-str key, or else try deserializing an owned
|
||||||
// string key
|
// string key
|
||||||
let k = map.next_key::<&str>().or_else(|_| {
|
let key = map.next_key::<&str>().or_else(|_| {
|
||||||
map.next_key::<String>().map(|k| {
|
map.next_key::<String>().map(|k| {
|
||||||
tmp = k;
|
tmp = k;
|
||||||
tmp.as_deref()
|
tmp.as_deref()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Ok(Some(k)) = k {
|
let this = key
|
||||||
let res = match k {
|
.ok()
|
||||||
"message" => {
|
.flatten()
|
||||||
map.next_value::<Message>().map(UpdateKind::Message).map_err(|_| false)
|
.and_then(|key| match key {
|
||||||
|
"message" => map.next_value::<Message>().ok().map(UpdateKind::Message),
|
||||||
|
"edited_message" => {
|
||||||
|
map.next_value::<Message>().ok().map(UpdateKind::EditedMessage)
|
||||||
|
}
|
||||||
|
"channel_post" => {
|
||||||
|
map.next_value::<Message>().ok().map(UpdateKind::ChannelPost)
|
||||||
|
}
|
||||||
|
"edited_channel_post" => {
|
||||||
|
map.next_value::<Message>().ok().map(UpdateKind::EditedChannelPost)
|
||||||
|
}
|
||||||
|
"inline_query" => {
|
||||||
|
map.next_value::<InlineQuery>().ok().map(UpdateKind::InlineQuery)
|
||||||
}
|
}
|
||||||
"edited_message" => map
|
|
||||||
.next_value::<Message>()
|
|
||||||
.map(UpdateKind::EditedMessage)
|
|
||||||
.map_err(|_| false),
|
|
||||||
"channel_post" => map
|
|
||||||
.next_value::<Message>()
|
|
||||||
.map(UpdateKind::ChannelPost)
|
|
||||||
.map_err(|_| false),
|
|
||||||
"edited_channel_post" => map
|
|
||||||
.next_value::<Message>()
|
|
||||||
.map(UpdateKind::EditedChannelPost)
|
|
||||||
.map_err(|_| false),
|
|
||||||
"inline_query" => map
|
|
||||||
.next_value::<InlineQuery>()
|
|
||||||
.map(UpdateKind::InlineQuery)
|
|
||||||
.map_err(|_| false),
|
|
||||||
"chosen_inline_result" => map
|
"chosen_inline_result" => map
|
||||||
.next_value::<ChosenInlineResult>()
|
.next_value::<ChosenInlineResult>()
|
||||||
.map(UpdateKind::ChosenInlineResult)
|
.ok()
|
||||||
.map_err(|_| false),
|
.map(UpdateKind::ChosenInlineResult),
|
||||||
"callback_query" => map
|
"callback_query" => {
|
||||||
.next_value::<CallbackQuery>()
|
map.next_value::<CallbackQuery>().ok().map(UpdateKind::CallbackQuery)
|
||||||
.map(UpdateKind::CallbackQuery)
|
}
|
||||||
.map_err(|_| false),
|
"shipping_query" => {
|
||||||
"shipping_query" => map
|
map.next_value::<ShippingQuery>().ok().map(UpdateKind::ShippingQuery)
|
||||||
.next_value::<ShippingQuery>()
|
}
|
||||||
.map(UpdateKind::ShippingQuery)
|
|
||||||
.map_err(|_| false),
|
|
||||||
"pre_checkout_query" => map
|
"pre_checkout_query" => map
|
||||||
.next_value::<PreCheckoutQuery>()
|
.next_value::<PreCheckoutQuery>()
|
||||||
.map(UpdateKind::PreCheckoutQuery)
|
.ok()
|
||||||
.map_err(|_| false),
|
.map(UpdateKind::PreCheckoutQuery),
|
||||||
"poll" => map.next_value::<Poll>().map(UpdateKind::Poll).map_err(|_| false),
|
"poll" => map.next_value::<Poll>().ok().map(UpdateKind::Poll),
|
||||||
"poll_answer" => map
|
"poll_answer" => {
|
||||||
.next_value::<PollAnswer>()
|
map.next_value::<PollAnswer>().ok().map(UpdateKind::PollAnswer)
|
||||||
.map(UpdateKind::PollAnswer)
|
}
|
||||||
.map_err(|_| false),
|
"my_chat_member" => {
|
||||||
"my_chat_member" => map
|
map.next_value::<ChatMemberUpdated>().ok().map(UpdateKind::MyChatMember)
|
||||||
.next_value::<ChatMemberUpdated>()
|
}
|
||||||
.map(UpdateKind::MyChatMember)
|
"chat_member" => {
|
||||||
.map_err(|_| false),
|
map.next_value::<ChatMemberUpdated>().ok().map(UpdateKind::ChatMember)
|
||||||
"chat_member" => map
|
}
|
||||||
.next_value::<ChatMemberUpdated>()
|
|
||||||
.map(UpdateKind::ChatMember)
|
|
||||||
.map_err(|_| false),
|
|
||||||
"chat_join_request" => map
|
"chat_join_request" => map
|
||||||
.next_value::<ChatJoinRequest>()
|
.next_value::<ChatJoinRequest>()
|
||||||
.map(UpdateKind::ChatJoinRequest)
|
.ok()
|
||||||
.map_err(|_| false),
|
.map(UpdateKind::ChatJoinRequest),
|
||||||
|
_ => Some(empty_error()),
|
||||||
|
})
|
||||||
|
.unwrap_or_else(empty_error);
|
||||||
|
|
||||||
_ => Err(true),
|
Ok(this)
|
||||||
};
|
|
||||||
|
|
||||||
let value_available = match res {
|
|
||||||
Ok(ok) => return Ok(ok),
|
|
||||||
Err(e) => e,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut value = serde_json::Map::new();
|
|
||||||
value.insert(
|
|
||||||
k.to_owned(),
|
|
||||||
if value_available {
|
|
||||||
map.next_value::<Value>().unwrap_or(Value::Null)
|
|
||||||
} else {
|
|
||||||
Value::Null
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
while let Ok(Some((k, v))) = map.next_entry::<_, Value>() {
|
|
||||||
value.insert(k, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(UpdateKind::Error(Value::Object(value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(UpdateKind::Error(Value::Object(<_>::default())))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,6 +338,10 @@ impl Serialize for UpdateKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn empty_error() -> UpdateKind {
|
||||||
|
UpdateKind::Error(Value::Object(<_>::default()))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
|
|
56
crates/teloxide-core/src/util.rs
Normal file
56
crates/teloxide-core/src/util.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
use crate::types::{MessageEntity, User};
|
||||||
|
|
||||||
|
/// Converts an optional iterator to a flattened iterator.
|
||||||
|
pub(crate) fn flatten<I>(opt: Option<I>) -> impl Iterator<Item = I::Item>
|
||||||
|
where
|
||||||
|
I: IntoIterator,
|
||||||
|
{
|
||||||
|
struct Flat<I>(Option<I>);
|
||||||
|
|
||||||
|
impl<I> Iterator for Flat<I>
|
||||||
|
where
|
||||||
|
I: Iterator,
|
||||||
|
{
|
||||||
|
type Item = I::Item;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.0.as_mut()?.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
match &self.0 {
|
||||||
|
None => (0, Some(0)),
|
||||||
|
Some(i) => i.size_hint(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Flat(opt.map(<_>::into_iter))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn mentioned_users_from_entities(
|
||||||
|
entities: &[MessageEntity],
|
||||||
|
) -> impl Iterator<Item = &User> {
|
||||||
|
use crate::types::MessageEntityKind::*;
|
||||||
|
|
||||||
|
entities.iter().filter_map(|entity| match &entity.kind {
|
||||||
|
TextMention { user } => Some(user),
|
||||||
|
|
||||||
|
Mention
|
||||||
|
| Hashtag
|
||||||
|
| Cashtag
|
||||||
|
| BotCommand
|
||||||
|
| Url
|
||||||
|
| Email
|
||||||
|
| PhoneNumber
|
||||||
|
| Bold
|
||||||
|
| Italic
|
||||||
|
| Underline
|
||||||
|
| Strikethrough
|
||||||
|
| Spoiler
|
||||||
|
| Code
|
||||||
|
| Pre { language: _ }
|
||||||
|
| TextLink { url: _ }
|
||||||
|
| CustomEmoji { custom_emoji_id: _ } => None,
|
||||||
|
})
|
||||||
|
}
|
|
@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## unreleased
|
## unreleased
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix `split` parser for tuple variants with len < 2 ([issue #834](https://github.com/teloxide/teloxide/issues/834))
|
||||||
|
|
||||||
## 0.7.1 - 2023-01-17
|
## 0.7.1 - 2023-01-17
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -125,8 +125,8 @@ fn parser_with_separator<'a>(
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
<#types>::from_str(s).map_err(|e| teloxide::utils::command::ParseError::IncorrectFormat(e.into()))?
|
<#types>::from_str(s).map_err(|e| teloxide::utils::command::ParseError::IncorrectFormat(e.into()))?
|
||||||
}
|
},
|
||||||
),*
|
)*
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -139,12 +139,12 @@ fn parser_with_separator<'a>(
|
||||||
let res = #res;
|
let res = #res;
|
||||||
|
|
||||||
match splitted.next() {
|
match splitted.next() {
|
||||||
Some(d) => ::std::result::Result::Err(teloxide::utils::command::ParseError::TooManyArguments {
|
Some(d) if !s.is_empty() => ::std::result::Result::Err(teloxide::utils::command::ParseError::TooManyArguments {
|
||||||
expected: #expected,
|
expected: #expected,
|
||||||
found: #expected + 1,
|
found: #expected + 1 + splitted.count(),
|
||||||
message: format!("Excess argument: {}", d),
|
message: format!("Excess argument: {}", d),
|
||||||
}),
|
}),
|
||||||
None => ::std::result::Result::Ok(res)
|
_ => ::std::result::Result::Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "teloxide"
|
name = "teloxide"
|
||||||
version = "0.12.0"
|
version = "0.12.2"
|
||||||
description = "An elegant Telegram bots framework for Rust"
|
description = "An elegant Telegram bots framework for Rust"
|
||||||
|
|
||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
|
@ -22,7 +22,9 @@ default = ["native-tls", "ctrlc_handler", "teloxide-core/default", "auto-send"]
|
||||||
webhooks = ["rand"]
|
webhooks = ["rand"]
|
||||||
webhooks-axum = ["webhooks", "axum", "tower", "tower-http"]
|
webhooks-axum = ["webhooks", "axum", "tower", "tower-http"]
|
||||||
|
|
||||||
sqlite-storage = ["sqlx"]
|
# FIXME: rename `sqlite-storage` -> `sqlite-storage-nativetls`
|
||||||
|
sqlite-storage = ["sqlx", "sqlx/runtime-tokio-native-tls", "native-tls"]
|
||||||
|
sqlite-storage-rustls = ["sqlx", "sqlx/runtime-tokio-rustls", "rustls"]
|
||||||
redis-storage = ["redis"]
|
redis-storage = ["redis"]
|
||||||
cbor-serializer = ["serde_cbor"]
|
cbor-serializer = ["serde_cbor"]
|
||||||
bincode-serializer = ["bincode"]
|
bincode-serializer = ["bincode"]
|
||||||
|
@ -35,7 +37,7 @@ native-tls = ["teloxide-core/native-tls"]
|
||||||
rustls = ["teloxide-core/rustls"]
|
rustls = ["teloxide-core/rustls"]
|
||||||
auto-send = ["teloxide-core/auto_send"]
|
auto-send = ["teloxide-core/auto_send"]
|
||||||
throttle = ["teloxide-core/throttle"]
|
throttle = ["teloxide-core/throttle"]
|
||||||
cache-me = ["teloxide-core/cache_me"]
|
cache-me = ["teloxide-core/cache_me"] # FIXME: why teloxide and core use - _ differently?
|
||||||
trace-adaptor = ["teloxide-core/trace_adaptor"]
|
trace-adaptor = ["teloxide-core/trace_adaptor"]
|
||||||
erased = ["teloxide-core/erased"]
|
erased = ["teloxide-core/erased"]
|
||||||
|
|
||||||
|
@ -44,8 +46,11 @@ erased = ["teloxide-core/erased"]
|
||||||
nightly = ["teloxide-core/nightly"]
|
nightly = ["teloxide-core/nightly"]
|
||||||
|
|
||||||
full = [
|
full = [
|
||||||
|
"webhooks",
|
||||||
"webhooks-axum",
|
"webhooks-axum",
|
||||||
"sqlite-storage",
|
"sqlite-storage",
|
||||||
|
# "sqlite-storage-rustls" is explicitly ommited here,
|
||||||
|
# since it conflicts with "sqlite-storage"
|
||||||
"redis-storage",
|
"redis-storage",
|
||||||
"cbor-serializer",
|
"cbor-serializer",
|
||||||
"bincode-serializer",
|
"bincode-serializer",
|
||||||
|
@ -63,7 +68,7 @@ full = [
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
teloxide-core = { version = "0.9.0", path = "../teloxide-core", default-features = false }
|
teloxide-core = { version = "0.9.1", path = "../teloxide-core", default-features = false }
|
||||||
teloxide-macros = { version = "0.7.1", path = "../teloxide-macros", optional = true }
|
teloxide-macros = { version = "0.7.1", path = "../teloxide-macros", optional = true }
|
||||||
|
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
@ -91,7 +96,6 @@ serde_with_macros = "1.4"
|
||||||
aquamarine = "0.1.11"
|
aquamarine = "0.1.11"
|
||||||
|
|
||||||
sqlx = { version = "0.6", optional = true, default-features = false, features = [
|
sqlx = { version = "0.6", optional = true, default-features = false, features = [
|
||||||
"runtime-tokio-native-tls",
|
|
||||||
"macros",
|
"macros",
|
||||||
"sqlite",
|
"sqlite",
|
||||||
] }
|
] }
|
||||||
|
@ -116,7 +120,8 @@ tokio-stream = "0.1"
|
||||||
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
# NB: can't use `all-features = true`, because `sqlite-storage` conflicts with `sqlite-storage-rustls`
|
||||||
|
features = ["full", "nightly"]
|
||||||
# FIXME: Add back "-Znormalize-docs" when https://github.com/rust-lang/rust/issues/93703 is fixed
|
# FIXME: Add back "-Znormalize-docs" when https://github.com/rust-lang/rust/issues/93703 is fixed
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
rustc-args = ["--cfg", "dep_docsrs"]
|
rustc-args = ["--cfg", "dep_docsrs"]
|
||||||
|
|
|
@ -207,12 +207,12 @@
|
||||||
//! [default]: DispatcherBuilder#method.default_handler
|
//! [default]: DispatcherBuilder#method.default_handler
|
||||||
//! [error]: DispatcherBuilder#method.error_handler
|
//! [error]: DispatcherBuilder#method.error_handler
|
||||||
//! [dialogues]: dialogue
|
//! [dialogues]: dialogue
|
||||||
//! [`examples/purchase.rs`]: https://github.com/teloxide/teloxide/blob/master/examples/purchase.rs
|
//! [`examples/purchase.rs`]: https://github.com/teloxide/teloxide/blob/master/crates/teloxide/examples/purchase.rs
|
||||||
//! [`Update::filter_message`]: crate::types::Update::filter_message
|
//! [`Update::filter_message`]: crate::types::Update::filter_message
|
||||||
//! [`Update::filter_callback_query`]: crate::types::Update::filter_callback_query
|
//! [`Update::filter_callback_query`]: crate::types::Update::filter_callback_query
|
||||||
//! [chain of responsibility]: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern
|
//! [chain of responsibility]: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern
|
||||||
//! [dependency injection (DI)]: https://en.wikipedia.org/wiki/Dependency_injection
|
//! [dependency injection (DI)]: https://en.wikipedia.org/wiki/Dependency_injection
|
||||||
//! [`examples/dispatching_features.rs`]: https://github.com/teloxide/teloxide/blob/master/examples/dispatching_features.rs
|
//! [`examples/dispatching_features.rs`]: https://github.com/teloxide/teloxide/blob/master/crates/teloxide/examples/dispatching_features.rs
|
||||||
//! [`Update`]: crate::types::Update
|
//! [`Update`]: crate::types::Update
|
||||||
|
|
||||||
pub mod dialogue;
|
pub mod dialogue;
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! [`examples/dialogue.rs`]: https://github.com/teloxide/teloxide/blob/master/examples/dialogue.rs
|
//! [`examples/dialogue.rs`]: https://github.com/teloxide/teloxide/blob/master/crates/teloxide/examples/dialogue.rs
|
||||||
|
|
||||||
#[cfg(feature = "redis-storage")]
|
#[cfg(feature = "redis-storage")]
|
||||||
pub use self::{RedisStorage, RedisStorageError};
|
pub use self::{RedisStorage, RedisStorageError};
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
| `native-tls` | Enables the [`native-tls`] TLS implementation (**enabled by default**). |
|
| `native-tls` | Enables the [`native-tls`] TLS implementation (**enabled by default**). |
|
||||||
| `rustls` | Enables the [`rustls`] TLS implementation. |
|
| `rustls` | Enables the [`rustls`] TLS implementation. |
|
||||||
| `redis-storage` | Enables the [Redis] storage support for dialogues. |
|
| `redis-storage` | Enables the [Redis] storage support for dialogues. |
|
||||||
| `sqlite-storage` | Enables the [Sqlite] storage support for dialogues. |
|
| `sqlite-storage` | Enables the [Sqlite] storage support for dialogues (depends on `native-tls`). |
|
||||||
|
| `sqlite-storage-rustls` | Enables the [Sqlite] storage support for dialogues (depends on `rustls`, conflicts with `sqlite-storage`). |
|
||||||
| `cbor-serializer` | Enables the [CBOR] serializer for dialogues. |
|
| `cbor-serializer` | Enables the [CBOR] serializer for dialogues. |
|
||||||
| `bincode-serializer` | Enables the [Bincode] serializer for dialogues. |
|
| `bincode-serializer` | Enables the [Bincode] serializer for dialogues. |
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
//!
|
//!
|
||||||
//! For a high-level overview, see [our GitHub repository](https://github.com/teloxide/teloxide).
|
//! For a high-level overview, see [our GitHub repository](https://github.com/teloxide/teloxide).
|
||||||
//!
|
//!
|
||||||
//! [[`examples/throw_dice.rs`](https://github.com/teloxide/teloxide/blob/master/examples/throw_dice.rs)]
|
//! [[`examples/throw_dice.rs`](https://github.com/teloxide/teloxide/blob/master/crates/teloxide/examples/throw_dice.rs)]
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! # #[cfg(feature = "ctrlc_handler")]
|
//! # #[cfg(feature = "ctrlc_handler")]
|
||||||
//! use teloxide::prelude::*;
|
//! use teloxide::prelude::*;
|
||||||
|
|
|
@ -9,7 +9,7 @@ use tokio::sync::mpsc;
|
||||||
use crate::{
|
use crate::{
|
||||||
requests::Requester,
|
requests::Requester,
|
||||||
stop::StopFlag,
|
stop::StopFlag,
|
||||||
types::Update,
|
types::{Update, UpdateKind},
|
||||||
update_listeners::{webhooks::Options, UpdateListener},
|
update_listeners::{webhooks::Options, UpdateListener},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -186,8 +186,14 @@ pub fn axum_no_setup(
|
||||||
Some(tx) => tx,
|
Some(tx) => tx,
|
||||||
};
|
};
|
||||||
|
|
||||||
match serde_json::from_str(&input) {
|
match serde_json::from_str::<Update>(&input) {
|
||||||
Ok(update) => {
|
Ok(mut update) => {
|
||||||
|
// See HACK comment in
|
||||||
|
// `teloxide_core::net::request::process_response::{closure#0}`
|
||||||
|
if let UpdateKind::Error(value) = &mut update.kind {
|
||||||
|
*value = serde_json::from_str(&input).unwrap_or_default();
|
||||||
|
}
|
||||||
|
|
||||||
tx.send(Ok(update)).expect("Cannot send an incoming update from the webhook")
|
tx.send(Ok(update)).expect("Cannot send an incoming update from the webhook")
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
|
|
@ -45,9 +45,9 @@
|
||||||
//! assert_eq!(args, vec!["3", "hours"]);
|
//! assert_eq!(args, vec!["3", "hours"]);
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! See [examples/admin_bot] as a more complicated examples.
|
//! See [examples/admin] as a more complicated examples.
|
||||||
//!
|
//!
|
||||||
//! [examples/admin_bot]: https://github.com/teloxide/teloxide/blob/master/examples/admin_bot/
|
//! [examples/admin]: https://github.com/teloxide/teloxide/blob/master/crates/teloxide/examples/admin.rs
|
||||||
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use std::{
|
use std::{
|
||||||
|
|
|
@ -117,7 +117,7 @@ fn parse_with_split() {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
DefaultCommands::Start(10, "hello".to_string()),
|
DefaultCommands::Start(10, "hello".to_string()),
|
||||||
DefaultCommands::parse("/start 10 hello", "").unwrap()
|
DefaultCommands::parse("/start 10 hello", "").unwrap(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +138,34 @@ fn parse_with_split2() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "macros")]
|
||||||
|
fn parse_with_split3() {
|
||||||
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
|
#[command(rename_rule = "lowercase")]
|
||||||
|
#[command(parse_with = "split")]
|
||||||
|
enum DefaultCommands {
|
||||||
|
Start(u8),
|
||||||
|
Help,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(DefaultCommands::Start(10), DefaultCommands::parse("/start 10", "").unwrap(),);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "macros")]
|
||||||
|
fn parse_with_split4() {
|
||||||
|
#[derive(BotCommands, Debug, PartialEq)]
|
||||||
|
#[command(rename_rule = "lowercase")]
|
||||||
|
#[command(parse_with = "split")]
|
||||||
|
enum DefaultCommands {
|
||||||
|
Start(),
|
||||||
|
Help,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(DefaultCommands::Start(), DefaultCommands::parse("/start", "").unwrap(),);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
fn parse_custom_parser() {
|
fn parse_custom_parser() {
|
||||||
|
|
Loading…
Add table
Reference in a new issue