diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index d46162ce..43e8b579 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -3,7 +3,7 @@ name: Feature request about: Suggest an idea for this project title: 'Feature Request: ' labels: K-feature-request -assignees: Hirrolot, WaffleLapkin +assignees: Hirrolot --- diff --git a/.github/ISSUE_TEMPLATE/parse-error.md b/.github/ISSUE_TEMPLATE/parse-error.md index 6a7b7766..332147b0 100644 --- a/.github/ISSUE_TEMPLATE/parse-error.md +++ b/.github/ISSUE_TEMPLATE/parse-error.md @@ -3,7 +3,6 @@ name: Parse error about: Report issue with `teloxide` parsing of telegram response title: 'Parse Error: ' labels: K-bug, FIXME, C-core -assignees: WaffleLapkin --- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9fab6bea..0ce272c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ env: # - down below in a matrix # - `Cargo.toml` # - **/CHANGELOG.md - rust_msrv: 1.70.0 + rust_msrv: 1.80.0 CI: 1 @@ -108,7 +108,7 @@ jobs: toolchain: nightly-2024-07-03 features: "--features full nightly" - rust: msrv - toolchain: 1.70.0 + toolchain: 1.80.0 features: "--features full" steps: @@ -138,8 +138,6 @@ jobs: - name: Downgrade deps for MSRV if: ${{ matrix.rust == 'msrv' }} run: | - cargo update -p sqlx --precise 0.7.3 - cargo update -p atomic-write-file --precise 0.1.2 exit 0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a4f794d..93ac5e9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add `filter_boost_added` and `filter_reply_to_story` filters to `MessageFilterExt` trait +- Add `filter_mention_command` filter to `HandlerExt` trait ([issue #494](https://github.com/teloxide/teloxide/issues/494)) +- Add `filter_business_connection`, `filter_business_message`, `filter_edited_business_message`, and `filter_deleted_business_messages` filters to update filters ([PR 1146](https://github.com/teloxide/teloxide/pull/1146)) + +### Changed + +- Environment bumps: ([#1147][pr1147]) + - MSRV (Minimal Supported Rust Version) was bumped from `1.70.0` to `1.80.0` + - Some dependencies was bumped: `sqlx` to `0.8.1`, `tower` to `0.5.0`, `reqwest` to `0.12.7` + - `tokio` version was explicitly specified as `1.39` + +[pr1147]: https://github.com/teloxide/teloxide/pull/1147 + +## 0.13.0 - 2024-08-16 + +### Added + - Documentation regarding the way captions work for the official clients on `SendMediaGroup` ([PR 992](https://github.com/teloxide/teloxide/pull/992)) -- Add `MessageToCopyNotFound` error to `teloxide::errors::ApiError` ([PR 917](https://github.com/teloxide/teloxide/pull/917)) +- Add `MessageToCopyNotFound` error to `teloxide::errors::ApiError` ([PR 917](https://github.com/teloxide/teloxide/pull/917)) - `Dispatcher::try_dispatch_with_listener` ([PR 913](https://github.com/teloxide/teloxide/pull/913)) -- Missing Message::filter_* functions ([PR 982](https://github.com/teloxide/teloxide/pull/982)): +- Missing Message::filter_* functions ([PR 982](https://github.com/teloxide/teloxide/pull/982)): - `filter_game` - `filter_venue` - `filter_video` @@ -43,12 +60,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `filter_video_chat_started` - `filter_video_chat_ended` - `filter_video_chat_participants_invited` - - `filter_web_app_data` + - `filter_web_app_data` - Implement `PostgresStorage`, a persistent dialogue storage based on [PostgreSQL](https://www.postgresql.org/)([PR 996](https://github.com/teloxide/teloxide/pull/996)). - Implement `GetChatId` for `teloxide_core::types::{Chat, ChatJoinRequest, ChatMemberUpdated}`. - Use [deadpool-redis](https://crates.io/crates/deadpool-redis) for Redis connection pooling ([PR 1081](https://github.com/teloxide/teloxide/pull/1081)). - Add `MessageExt::filter_story` method for the corresponding `MediaKind::Story` variant ([PR 1087](https://github.com/teloxide/teloxide/pull/1087)). - Add `update_listeners::webhooks::Options::path`, an option to make the webhook server listen on a different path, which can be useful behind a reverse proxy. +- Add `filter_giveaway`, `filter_giveaway_completed`, `filter_giveaway_created` and `filter_giveaway_winners` filters to `MessageFilterExt` trait ([PR 1101](https://github.com/teloxide/teloxide/pull/1101)) - Add `teloxide::utils::command::BotCommands` to the `prelude` ([PR 1123](https://github.com/teloxide/teloxide/pull/1123)) ### Fixed @@ -70,6 +88,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - MSRV (Minimal Supported Rust Version) was bumped from `1.68.0` to `1.70.0` ([PR 996][https://github.com/teloxide/teloxide/pull/996]) - `axum` was bumped to `0.7`, along with related libraries used for webhooks ([PR 1093][https://github.com/teloxide/teloxide/pull/1093]) - `Polling`'s exponential backoff now results in 64 seconds maximum delay instead of 17 minutes ([PR 1113][https://github.com/teloxide/teloxide/pull/1113]) +- `filter_forward_from` was renamed to `filter_forward_origin` and now returns `MessageOrigin` instead of `ForwardFrom` ([PR 1101](https://github.com/teloxide/teloxide/pull/1101)) ### Removed diff --git a/Cargo.lock b/Cargo.lock index 23d9fb70..ff7098ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,7 +24,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", "once_cell", "version_check", "zerocopy", @@ -104,15 +103,10 @@ dependencies = [ ] [[package]] -name = "atty" -version = "0.2.14" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" @@ -130,10 +124,10 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http 1.1.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", - "hyper 1.4.1", + "hyper", "hyper-util", "itoa", "matchit", @@ -148,7 +142,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper 1.0.1", "tokio", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -163,8 +157,8 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.1.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", @@ -190,18 +184,18 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "base64" version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bincode" version = "1.3.3" @@ -222,6 +216,9 @@ name = "bitflags" version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +dependencies = [ + "serde", +] [[package]] name = "block-buffer" @@ -238,6 +235,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytemuck" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31" + [[package]] name = "byteorder" version = "1.5.0" @@ -246,15 +249,18 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cc" -version = "1.1.5" +version = "1.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324c74f2155653c90b04f25b2a47a8a631360cb908f92a772695f430c7e31052" +checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -273,7 +279,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-targets 0.52.6", ] [[package]] @@ -290,6 +296,15 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -317,15 +332,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" dependencies = [ "libc", ] @@ -429,9 +444,9 @@ dependencies = [ [[package]] name = "deadpool-runtime" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49" +checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" dependencies = [ "tokio", ] @@ -449,6 +464,12 @@ dependencies = [ "syn 2.0.52", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -493,26 +514,13 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "env_logger" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -dependencies = [ - "atty", - "humantime 1.3.0", - "log", - "regex", - "termcolor", -] - [[package]] name = "env_logger" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ - "humantime 2.1.0", + "humantime", "is-terminal", "log", "regex", @@ -558,15 +566,20 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.5.3" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "flume" @@ -738,16 +751,16 @@ checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "h2" -version = "0.3.26" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", - "http 0.2.12", + "http", "indexmap 2.2.5", "slab", "tokio", @@ -769,9 +782,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", @@ -779,11 +792,11 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.8.4" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -791,18 +804,12 @@ name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", -] [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -810,6 +817,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -843,17 +856,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.1.0" @@ -865,17 +867,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -883,7 +874,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.1.0", + "http", ] [[package]] @@ -894,8 +885,8 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.1.0", - "http-body 1.0.1", + "http", + "http-body", "pin-project-lite", ] @@ -911,45 +902,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "humantime" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error", -] - [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" -[[package]] -name = "hyper" -version = "0.14.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.4.1" @@ -959,56 +917,71 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "smallvec", "tokio", + "want", ] [[package]] name = "hyper-rustls" -version = "0.24.2" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ "futures-util", - "http 0.2.12", - "hyper 0.14.30", + "http", + "hyper", + "hyper-util", "rustls", + "rustls-native-certs", + "rustls-pki-types", "tokio", "tokio-rustls", + "tower-service", + "webpki-roots", ] [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", - "hyper 0.14.30", + "http-body-util", + "hyper", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", ] [[package]] name = "hyper-util" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" dependencies = [ "bytes", + "futures-channel", "futures-util", - "http 1.1.0", - "http-body 1.0.1", - "hyper 1.4.1", + "http", + "http-body", + "hyper", "pin-project-lite", + "socket2", "tokio", + "tower 0.4.13", + "tower-service", + "tracing", ] [[package]] @@ -1093,7 +1066,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -1104,11 +1077,11 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi 0.4.0", "libc", "windows-sys 0.52.0", ] @@ -1130,24 +1103,24 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.155" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libsqlite3-sys" -version = "0.27.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -1231,13 +1204,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi 0.3.9", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1288,9 +1262,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.1" +version = "0.36.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" dependencies = [ "memchr", ] @@ -1345,6 +1319,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1365,7 +1345,7 @@ dependencies = [ "libc", "redox_syscall 0.5.2", "smallvec", - "windows-targets 0.52.4", + "windows-targets 0.52.6", ] [[package]] @@ -1420,18 +1400,21 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] -name = "pretty_env_logger" -version = "0.4.0" +name = "pretty_assertions" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" dependencies = [ - "env_logger 0.7.1", - "log", + "diff", + "yansi", ] [[package]] @@ -1440,7 +1423,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" dependencies = [ - "env_logger 0.10.2", + "env_logger", "log", ] @@ -1478,16 +1461,58 @@ dependencies = [ ] [[package]] -name = "quick-error" -version = "1.2.3" +name = "quinn" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash", + "rustls", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1570,9 +1595,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick 1.1.2", "memchr", @@ -1599,21 +1624,23 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" -version = "0.11.27" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.30", + "http", + "http-body", + "http-body-util", + "hyper", "hyper-rustls", "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", @@ -1623,13 +1650,15 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "quinn", "rustls", "rustls-native-certs", "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.1", "system-configuration", "tokio", "tokio-native-tls", @@ -1642,7 +1671,16 @@ dependencies = [ "wasm-streams", "web-sys", "webpki-roots", - "winreg", + "windows-registry", +] + +[[package]] +name = "rgb" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f86ae463694029097b846d8f99fd5536740602ae00022c0c50c5600720b2f71" +dependencies = [ + "bytemuck", ] [[package]] @@ -1662,13 +1700,14 @@ dependencies = [ [[package]] name = "ron" -version = "0.7.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ - "base64 0.13.1", - "bitflags 1.3.2", + "base64 0.21.7", + "bitflags 2.4.2", "serde", + "serde_derive", ] [[package]] @@ -1677,6 +1716,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc_version" version = "0.4.0" @@ -1701,44 +1746,55 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.12" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ - "log", + "once_cell", "ring", + "rustls-pki-types", "rustls-webpki", - "sct", + "subtle", + "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.6.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +checksum = "04182dffc9091a404e0fc069ea5cd60e5b866c3adf881eff99a32d048242dffa" dependencies = [ "openssl-probe", "rustls-pemfile", + "rustls-pki-types", "schannel", "security-framework", ] [[package]] name = "rustls-pemfile" -version = "1.0.4" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", + "rustls-pki-types", ] [[package]] -name = "rustls-webpki" -version = "0.101.7" +name = "rustls-pki-types" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + +[[package]] +name = "rustls-webpki" +version = "0.102.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ "ring", + "rustls-pki-types", "untrusted", ] @@ -1769,16 +1825,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "security-framework" version = "2.10.0" @@ -1810,9 +1856,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.204" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] @@ -1829,9 +1875,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", @@ -1840,11 +1886,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1904,6 +1951,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -1927,12 +1980,15 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1959,9 +2015,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +checksum = "fcfa89bea9500db4a0d038513d7a060566bfc51d46d1c014847049a45cce85e8" dependencies = [ "sqlx-core", "sqlx-macros", @@ -1971,11 +2027,10 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +checksum = "d06e2f2bd861719b1f3f0c7dbe1d80c30bf59e76cf019f07d9014ed7eefb8e08" dependencies = [ - "ahash", "atoi", "byteorder", "bytes", @@ -1988,6 +2043,7 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", + "hashbrown 0.14.5", "hashlink", "hex", "indexmap 2.2.5", @@ -2014,26 +2070,26 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +checksum = "2f998a9defdbd48ed005a89362bd40dd2117502f15294f61c8d47034107dbbdc" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 1.0.109", + "syn 2.0.52", ] [[package]] name = "sqlx-macros-core" -version = "0.7.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +checksum = "3d100558134176a2629d46cec0c8891ba0be8910f7896abfdb75ef4ab6f4e7ce" dependencies = [ "dotenvy", "either", - "heck", + "heck 0.5.0", "hex", "once_cell", "proc-macro2", @@ -2044,7 +2100,7 @@ dependencies = [ "sqlx-core", "sqlx-postgres", "sqlx-sqlite", - "syn 1.0.109", + "syn 2.0.52", "tempfile", "tokio", "url", @@ -2052,12 +2108,12 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +checksum = "9734dbce698c67ecf67c442f768a5e90a49b2a4d61a9f1d59f73874bd4cf0710" dependencies = [ "atoi", - "base64 0.21.7", + "base64 0.22.1", "bitflags 2.4.2", "byteorder", "crc", @@ -2090,9 +2146,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +checksum = "a75b419c3c1b1697833dd927bdc4c6545a620bc1bbafabd44e1efbe9afcd337e" dependencies = [ "atoi", "flume", @@ -2105,10 +2161,10 @@ dependencies = [ "log", "percent-encoding", "serde", + "serde_urlencoded", "sqlx-core", "tracing", "url", - "urlencoding", ] [[package]] @@ -2167,23 +2223,26 @@ name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] name = "system-configuration" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.2", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", @@ -2203,7 +2262,7 @@ checksum = "20f34339676cdcab560c9a82300c4c2581f68b9369aedf0fae86f2ff9565ff3e" [[package]] name = "teloxide" -version = "0.12.2" +version = "0.13.0" dependencies = [ "aquamarine", "axum", @@ -2218,7 +2277,7 @@ dependencies = [ "log", "mime", "pin-project", - "pretty_env_logger 0.5.0", + "pretty_env_logger", "rand", "reqwest", "serde", @@ -2226,19 +2285,19 @@ dependencies = [ "serde_json", "sqlx", "teloxide-core", - "teloxide-macros", + "teloxide-macros 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror", "tokio", "tokio-stream", "tokio-util", - "tower", + "tower 0.5.0", "tower-http", "url", ] [[package]] name = "teloxide-core" -version = "0.9.1" +version = "0.10.1" dependencies = [ "aho-corasick 0.7.20", "bitflags 1.3.2", @@ -2254,9 +2313,11 @@ dependencies = [ "mime", "once_cell", "pin-project", - "pretty_env_logger 0.4.0", + "pretty_assertions", + "pretty_env_logger", "rc-box", "reqwest", + "rgb", "ron", "serde", "serde_json", @@ -2274,9 +2335,21 @@ dependencies = [ [[package]] name = "teloxide-macros" -version = "0.7.1" +version = "0.8.0" dependencies = [ - "heck", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "teloxide-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e2d33d809c3e7161a9ab18bedddf98821245014f0a78fa4d2c9430b2ec018c1" +dependencies = [ + "heck 0.4.1", "proc-macro2", "quote", "syn 1.0.109", @@ -2284,14 +2357,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2305,18 +2379,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.62" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.62" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", @@ -2340,27 +2414,26 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.39.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", @@ -2379,11 +2452,12 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.24.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ "rustls", + "rustls-pki-types", "tokio", ] @@ -2427,6 +2501,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36b837f86b25d7c0d7988f00a54e74739be6477f2aac6201b8f429a7569991b7" +dependencies = [ + "tower-layer", + "tower-service", +] + [[package]] name = "tower-http" version = "0.5.2" @@ -2435,8 +2519,8 @@ checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "bitflags 2.4.2", "bytes", - "http 1.1.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "pin-project-lite", "tower-layer", @@ -2446,15 +2530,15 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -2532,15 +2616,9 @@ dependencies = [ [[package]] name = "unicode-properties" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" - -[[package]] -name = "unicode-segmentation" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" [[package]] name = "unicode_categories" @@ -2566,12 +2644,6 @@ dependencies = [ "serde", ] -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - [[package]] name = "uuid" version = "1.10.0" @@ -2595,9 +2667,9 @@ checksum = "c4808a28789238714a29163e4cb8031f0f050dd670f7a0cc74b6d80f3ce343fa" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "want" @@ -2622,19 +2694,20 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", @@ -2647,9 +2720,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -2659,9 +2732,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2669,9 +2742,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", @@ -2682,9 +2755,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-streams" @@ -2701,9 +2774,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -2711,9 +2784,12 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.4" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "whoami" @@ -2725,44 +2801,52 @@ dependencies = [ "wasite", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", ] [[package]] @@ -2780,7 +2864,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -2800,17 +2893,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2821,9 +2915,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2833,9 +2927,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2845,9 +2939,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2857,9 +2957,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2869,9 +2969,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2881,9 +2981,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2893,19 +2993,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" - -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "xshell" @@ -2922,12 +3012,19 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d422e8e38ec76e2f06ee439ccc765e9c6a9638b9e7c9f2e8255e4d41e8bd852" +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] @@ -2941,3 +3038,9 @@ dependencies = [ "quote", "syn 2.0.52", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 37e4f9f1..18c17a48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "2" # The settings below will be applied to all crates in the workspace [workspace.package] # MSRV (minimal supported Rust version). -rust-version = "1.70" +rust-version = "1.80" edition = "2021" license = "MIT" diff --git a/README.md b/README.md index 6c19b5aa..35b8bc87 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -> [v0.11 -> v0.12 migration guide >>](MIGRATION_GUIDE.md#011---012) -

teloxide

@@ -13,7 +11,7 @@ - + @@ -58,7 +56,7 @@ $ set TELOXIDE_TOKEN= $ $env:TELOXIDE_TOKEN= ``` - 4. Make sure that your Rust compiler is up to date (`teloxide` currently requires rustc at least version 1.70): + 4. Make sure that your Rust compiler is up to date (`teloxide` currently requires rustc at least version 1.80): ```bash # If you're using stable $ rustup update stable @@ -72,9 +70,9 @@ $ rustup override set nightly 5. Run `cargo new my_bot`, enter the directory and put these lines into your `Cargo.toml`: ```toml [dependencies] -teloxide = { version = "0.12", features = ["macros"] } +teloxide = { version = "0.13", features = ["macros"] } log = "0.4" -pretty_env_logger = "0.4" +pretty_env_logger = "0.5" tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] } ``` @@ -356,7 +354,7 @@ Feel free to propose your own bot to our collection! -See [1600+ other public repositories using `teloxide` >>](https://github.com/teloxide/teloxide/network/dependents) +See [1900+ other public repositories using `teloxide` >>](https://github.com/teloxide/teloxide/network/dependents) ## Contributing diff --git a/crates/teloxide-core/CHANGELOG.md b/crates/teloxide-core/CHANGELOG.md index 262f99cc..7d3eb13d 100644 --- a/crates/teloxide-core/CHANGELOG.md +++ b/crates/teloxide-core/CHANGELOG.md @@ -9,6 +9,80 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Support for TBA 7.1 ([#1131](pr1131)) + - Updated docs for `can_post_stories`, `can_edit_stories` and `can_delete_stories` admin privileges + - Add `ChatBoostAdded` and `StoryId` structs + - Add `ChatBoostAdded` variant to `MessageKind` enum + - Add `sender_boost_count` and `reply_to_story` fields to `MessageCommon` struct + - Add `chat` and `id` fields to `Story` struct + - Add `unrestrict_boost_count` and `custom_emoji_sticker_set_name` fields to `PublicChatSupergroup` struct + - Add `boost_added` and `reply_to_story` getters to `Message` struct + - Add `unrestrict_boost_count` and `custom_emoji_sticker_set_name` getters to `Chat` struct + +- Support for TBA 7.2 ([#1146](pr1146)) + - Update documentation of `SendSticker` method + - Add `is_from_offline` field to `MessageCommon` struct + - Add `can_connect_to_business` field to `Me` struct + - Add `personal_chat` field to `ChatPrivate` struct + - Add `ReplaceStickerInSet` and `GetBusinessConnection` methods + - Add `Birthdate` struct and corresponding field `birthdate` in `ChatPrivate` + - Add `request_name`, `request_username` and `request_photo` fields to `KeyboardButtonRequestUsers` struct + - Add `request_title`, `request_username` and `request_photo` fields to `KeyboardButtonRequestChat` struct + - Add `SharedUser` struct + - Add `title`, `username` and `photo` fields to `ChatShared` struct + - Add `format` field to `InputSticker` struct + - Add `format` parameter to `SetStickerSetThumbnail` method + - Add `BusinessConnectionId` struct + - Add `business_connection_id` parameter to `SendMessage`, `SendPhoto`, `SendVideo`, `SendAnimation`, `SendAudio`, `SendDocument`, `SendSticker`, `SendVideoNote`, `SendVoice`, `SendLocation`, `SendVenue`, `SendContact`, `SendPoll`, `SendDice`, `SendGame`, and `SendMediaGroup` methods + - Add `sender_business_bot` and `business_connection_id` fields to `Message` struct + - Add `BusinessIntro` struct and corresponding field `business_intro` to `ChatPrivate` struct + - Add `BusinessLocation` struct and corresponding field `business_location` to `ChatPrivate` struct + - Add `BusinessOpeningHoursInterval` and `BusinessOpeningHours` structs and corresponding field `business_opening_hours` to `ChatPrivate` struct + - Add `BusinessConnection` struct + - Add `BusinessMessagesDeleted` struct + - Add `BusinessConnection`, `BusinessMessage`, `EditedBusinessMessage` and `DeletedBusinessMessages` variants to `UpdateKind` enum + +### Changed + +- `MaybeAnonymousUser` type introduced, which replaced `PollAnswer::voter: Voter` and `MessageReactionUpdated::{user, actor_chat}` in `MessageReactionUpdated`([#1134][pr1134]) +- Environment bumps: ([#1147][pr1147]) + - MSRV (Minimal Supported Rust Version) was bumped from `1.70.0` to `1.80.0` + - Some dependencies was bumped: `reqwest` to `0.12.7` and `ron` to `0.8.1` + - `tokio` version was explicitly specified as `1.39` and feature `io-util` was enabled for it +- `[u8; 3]` sometimes used for RGB values was replaced with dedicated `Rgb` struct: ([#1151][pr1151]) + - `serde_rgb` module from `types.rs` file was removed + - `CreateForumTopic`, `ForumTopicCreated` and `ForumTopic` structs now use `Rgb` instead of `[u8; 3]` for `icon_color` field + - Added `rgb` crate dependency to Cargo.toml + - Added `Rgb` struct with `From` implementation for `RGB8` type from popular `rgb` crate + +- Support for TBA 7.2 ([#1146](pr1146)) + - Remove `flags` field from `StickerSet` struct + - Remove `sticker_format` parameter from `CreateNewStickerSet` method + - Wrap `Public` variant of `ChatKind` in `Box` + - Replaced `user_ids` with `users` in `UsersShared` struct + +[pr1131]: https://github.com/teloxide/teloxide/pull/1131 +[pr1134]: https://github.com/teloxide/teloxide/pull/1134 +[pr1146]: https://github.com/teloxide/teloxide/pull/1146 +[pr1147]: https://github.com/teloxide/teloxide/pull/1147 +[pr1151]: https://github.com/teloxide/teloxide/pull/1151 + +### Removed + +- `Currency` enum removed, its usages replaced with `String`. + +## 0.10.1 - 2024-08-17 + +### Fixed +- Issue, when using `ReplyParameters` and multipart-requests involving file-sending it failed with `unimplemented error` ([#1136][pr1136], issue [#1135][issue1135]) + +[pr1136]: https://github.com/teloxide/teloxide/pull/1136 +[issue1135]: https://github.com/teloxide/teloxide/issues/1135 + +## 0.10.0 - 2024-08-16 + +### 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]) @@ -19,7 +93,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `ChatId::as_user` ([#905][pr905]) - Implement `PartialEq for UserId` and `PartialEq for ChatId` ([#905][pr905]) - `ChatId::{MIN, MAX}` ([#905][pr905]) -- Missing `Message` getters ([#982][pr982]): +- Missing `Message` getters ([#982][pr982]): - `message_auto_delete_timer_changed` - `write_access_allowed` - `forum_topic_created` @@ -31,7 +105,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `video_chat_scheduled` - `video_chat_started` - `video_chat_ended` - - `web_app_data` + - `web_app_data` - `is_delete_chat_photo`, `is_group_chat_created`, `is_super_group_chat_created`, `is_channel_chat_created` functions to `Message` ([#982][pr982]) - Support for TBA 6.5 ([#954][pr954]) - Add `can_send_audios`, `can_send_documents`, `can_send_photos`, `can_send_videos`, `can_send_video_notes`, and `can_send_voice_notes` to `ChatPermissions` and `Restricted` @@ -43,7 +117,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `shared_chat` method to `Message` - Add `KeyboardButtonRequestUser` and `UserShared` types - Add `RequestUser` variant to `ButtonRequest` - - Add `UserShared` variant to `MessageKind` + - Add `UserShared` variant to `MessageKind` - Add `shared_user` method to `Message` - Support for TBA 6.6 ([#1040](pr1040)) - Add methods for working with bot's description: @@ -61,7 +135,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add parameter `emoji` to the `send_sticker` method to specify an emoji for just uploaded stickers - Add support for the creation of custom emoji sticker sets in `create_new_sticker_set` - Add parameter `needs_repainting` to the `create_new_sticker_set` methodto automatically change the color of emoji based on context (e.g., use text color in messages, accent color in statuses, etc.) - - Add field `needs_repainting` to the `Sticker` struct + - Add field `needs_repainting` to the `Sticker` struct - Add support for the creation of sticker sets with multiple initial stickers in `create_new_sticker_set` by replacing the parameters `sticker`, `emojis` and `mask_position` with the parameters `stickers` and `sticker_format`. - Add support for .WEBP files in `create_new_sticker_set` and `add_sticker_to_set` - Add support for .WEBP, .TGS, and .WEBM files in `upload_sticker_file` by replacing the parameter `png_sticker` with the parameters `sticker` and `sticker_format` @@ -88,6 +162,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for TBA 6.9 ([#1095](pr1095)) - Add `can_post_stories`, `can_edit_stories` and `can_delete_stories` fields to `ChatMemberKind::Administrator`, `ChatAdministratorRights` and `PromoteChatMember` - Add `from_request` and `from_attachment_menu` fields to `WriteAccessAllowed` +- Support for TBA 7.0 ([#1101](pr1101)) + - Reactions: + - Add `ReactionType` enum + - Add `MessageReactionUpdated` and `MessageReactionCountUpdated` structs + - Add `MessageReaction` and `MessageReactionCount` variants to `UpdateKind` enum + - Add `filter_message_reaction_updated` and `filter_message_reaction_count_updated` filters to `UpdateFilterExt` trait + - Add `set_message_reaction` TBA method to `Requester` trait + - Add `available_reactions` field to `Chat` struct + - Replies 2.0 + - Add the fields `MessageCommon::{external_reply, quote}` of types `ExternalReplyInfo` and `TextQuote` respectively + - Link Preview Customization + - `disable_web_page_preview` replaced with `link_preview_options`: + - Remove `disable_web_page_preview` field from `send_message` and `send_message` TBA methods and `InputMessageContentText` struct + - Add `LinkPreviewOptions` struct + - Add `link_preview_options` field to `InputMessageContentText` and `Message` structs + - Add `link_preview_options` field to `send_message` and `send_message` TBA methods + - Multiple Message Actions + - Add TBA methods `delete_messages`, `forward_messages` and `copy_messages` to `Requester` trait + - Chat Boost + - Add `ChatBoostSource` enum + - Add `ChatBoost`, `ChatBoostUpdated`, `ChatBoostRemoved` and `UserChatBoosts` structs + - Add `ChatBoost` and `RemovedChatBoost` variants to `UpdateKind` enum + - Add `filter_chat_boost` and `filter_removed_chat_boost` filters to `UpdateFilterExt` trait + - Add `get_user_chat_boosts` TBA method to `Requester` trait + - Giveaway: + - Add `Giveaway`, `GiveawayCreated`, `GiveawayWinners` and `GiveawayCompleted` structs + - Add `Giveaway`, `GiveawayCreated`, `GiveawayWinners` and `GiveawayCompleted` variants to `MessageKind` enum + - Add `giveaway`, `giveaway_created`, `giveaway_winners` and `giveaway_completed` getters to `Message` + - Other Changes + - Add fields `ChafFullInfo::{has_visible_history, accent_color_id, background_custom_emoji_id, profile_accent_color_id, profile_background_custom_emoji_id}` + - Add `RequestId` type + - Add `CallbackQuery::regular_message` getter [pr851]: https://github.com/teloxide/teloxide/pull/851 [pr887]: https://github.com/teloxide/teloxide/pull/887 @@ -97,6 +203,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [pr1086]: https://github.com/teloxide/teloxide/pull/1086 [pr1087]: https://github.com/teloxide/teloxide/pull/1087 [pr1095]: https://github.com/teloxide/teloxide/pull/1095 +[pr1101]: https://github.com/teloxide/teloxide/pull/1101 ### Fixed @@ -108,6 +215,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Deserialization of empty (content-less) messages that can sometimes appear as a part of callback query ([#850][pr850], issue [#873][issue873]) - Type of `chat_id` in `send_game`: `u32` => `ChatId` ([#1066][pr1066]) - `Requester::Err` was bounded to the `AsResponseParameters`. In case of `RetryAfter(..)` errors the polling is paused for the specified delay instead of falling into the backoff strategy ([#1113][pr1113]) +- `SendPoll` documentation ([#1101][pr1101], issue [#1058][issue1058]) +- `from`, `sender_chat` and `is_topic_message` are moved into the `Message` from the `MessageCommon` ([#1101][pr1101], issue [#945][issue945]). Initially [#946][pr946] +- Add support for `blockquote` entity in received messages + in `MarkdownV2` and `HTML` ([#1101][pr1101], issue [#1062][issue1062]) [pr839]: https://github.com/teloxide/teloxide/pull/839 [pr879]: https://github.com/teloxide/teloxide/pull/879 @@ -131,7 +241,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `InlineQueryResultLocation::live_period` - `Location::live_period` - `MessageAutoDeleteTimerChanged::message_auto_delete_time` - - `Poll::open_period` + - `Poll::open_period` - `Video::duration` - `VideoNote::duration` - `Voice::duration` @@ -169,6 +279,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Method `set_sticker_set_thumb` and it's parameter `thumb` have been renamed to `set_sticker_set_thumbnail` and `thumbnail` respectively - Fields `{InlineQueryResultArticle, InlineQueryResultContact, InlineQueryResultDocument, InlineQueryResultLocation, InlineQueryResultVenue}::{thumb_url, thumb_width, thumb_height}` have been renamed to `{thumbnail_url, thumbnail_width, thumbnail_height}` respectively - Field `{InlineQueryResultPhoto, InlineQueryResultVideo, InlineQueryResultGif, InlineQueryResultMpeg4Gif}::thumb_url` has been renamed to `thumbnail_url` +- Support for TBA 7.0 ([#1101](pr1101)) + - Replies 2.0 + - Parameter `reply_parameters` of type `ReplyParameters` replaces parameters `reply_to_message_id` and `allow_sending_without_reply` in the methods: + - `copy_message` + - `send_message` + - `send_photo` + - `send_video` + - `send_animation` + - `send_audio` + - `send_document` + - `send_sticker` + - `send_video_note` + - `send_voice` + - `send_location` + - `send_venue` + - `send_contact` + - `send_poll` + - `send_dice` + - `send_invoice` + - `send_game` + - `send_media_group` + - Request for multiple users + - Struct `KeyboardButtonRequestUser` was renamed to `KeyboardButtonRequestUsers` + added field `max_quantity` to it + - Field `KeyboardButton::request_user` was renamed to `request_users` + - `MessageUserShared` was renamed to `MessageUsersShared` + - Other Changes + - `Message::pinned_message` and `CallbackQuery::message` now have `MaybeInaccessibleMessage` type + - Field `emoji_status_custom_emoji_id` is allowed in non-private chats (moved to the `ChatFullInfo`) + - Struct `Forward` was replaced by `MessageOrigin` in `MessageCommon` + - `RequestId` replaces `i32` in `ChatShared` and `KeyboardButtonRequestChat` structs + [pr852]: https://github.com/teloxide/teloxide/pull/853 [pr859]: https://github.com/teloxide/teloxide/pull/859 @@ -182,8 +323,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Deprecated - `Update::user`, use `Update::from` instead ([#850][pr850]) +- `Message::from()` and `Message::sender_chat()` in favour of fields with the same name([initially #946][pr946][#1101][pr1101]) [pr850]: https://github.com/teloxide/teloxide/pull/850 +[pr946]: https://github.com/teloxide/teloxide/pull/946 ### Removed @@ -209,7 +352,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 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]) - In cases when `teloxide` can't deserialize an update, error now includes the full json value ([#826][pr826]) diff --git a/crates/teloxide-core/Cargo.toml b/crates/teloxide-core/Cargo.toml index af60953f..6c30efef 100644 --- a/crates/teloxide-core/Cargo.toml +++ b/crates/teloxide-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teloxide-core" -version = "0.9.1" +version = "0.10.1" description = "Core part of the `teloxide` library - telegram bot API client" rust-version.workspace = true @@ -49,11 +49,11 @@ full = ["throttle", "trace_adaptor", "erased", "cache_me"] [dependencies] futures = "0.3.5" -tokio = { version = "1.12.0", features = ["fs"] } +tokio = { version = "1.39", features = ["fs", "io-util"] } tokio-util = { version = "0.7.0", features = ["codec"] } pin-project = "1.0.12" bytes = "1.0.0" -reqwest = { version = "0.11.10", features = [ +reqwest = { version = "0.12.7", features = [ "json", "stream", "multipart", @@ -76,12 +76,13 @@ rc-box = "1.1.1" chrono = { version = "0.4.32", default-features = false } either = "1.6.1" bitflags = { version = "1.2" } +rgb = "0.8.48" vecrem = { version = "0.1", optional = true } [dev-dependencies] -pretty_env_logger = "0.4" +pretty_env_logger = "0.5" tokio = { version = "1.8.0", features = [ "fs", "macros", @@ -91,10 +92,11 @@ tokio = { version = "1.8.0", features = [ cool_asserts = "2.0.3" xshell = "0.2" -ron = "0.7" +ron = "0.8.1" indexmap = { version = "1.9", features = ["serde-1"] } aho-corasick = "0.7" itertools = "0.10" +pretty_assertions = "1.4.0" [package.metadata.docs.rs] diff --git a/crates/teloxide-core/README.md b/crates/teloxide-core/README.md index d4748be4..201aff8e 100644 --- a/crates/teloxide-core/README.md +++ b/crates/teloxide-core/README.md @@ -12,7 +12,7 @@ - + @@ -25,9 +25,9 @@
```toml -teloxide-core = "0.9" +teloxide-core = "0.10.1" ``` -_Compiler support: requires rustc 1.68+_. +_Compiler support: requires rustc 1.80+_. [`teloxide`]: https://docs.rs/teloxide [Telegram Bot API]: https://core.telegram.org/bots/api diff --git a/crates/teloxide-core/schema.ron b/crates/teloxide-core/schema.ron index 75a3a639..c1216ca7 100644 --- a/crates/teloxide-core/schema.ron +++ b/crates/teloxide-core/schema.ron @@ -1,14 +1,14 @@ //! This file is written in [RON] (Rusty Object Notation). -//! -//! This "schema" is a formalized version of the +//! +//! This "schema" is a formalized version of the //! [telegram bot api documentation][tbadoc] which is not machine readable. //! (note: this schema currently covers only API methods and **not** types). -//! +//! //! Also, note that this file is **hand written** and may contain typos, //! deviations from original doc, and other kinds of typical human errors. //! If you found an error please open an issue (or make a PR) on [github]. -//! -//! This schema is targeting code generation for API wrappers in a statically +//! +//! This schema is targeting code generation for API wrappers in a statically //! typed language, though you may use it whatever you want. //! //! This scheme also has some intentional differences from original doc: @@ -20,26 +20,26 @@ //! + `AllowedUpdate` inner type of `allowed_updates` in `getUpdates` and //! `setWebhook` (so type is `ArrayOf(AllowedUpdate)`) //! + `ReplyMarkup` - type of `reply_markup` parameter, in the original -//! documentation written as `InlineKeyboardMarkup or ReplyKeyboardMarkup or +//! documentation written as `InlineKeyboardMarkup or ReplyKeyboardMarkup or //! ReplyKeyboardRemove or ForceReply` //! + `ParseMode` type of `parse_mode` params //! + `PollType` type of poll, either โ€œquizโ€ or โ€œregularโ€ //! + `DiceEmoji` emoji that can be used in `sendDice` one of โ€œ๐ŸŽฒโ€, โ€œ๐ŸŽฏโ€, or โ€œ๐Ÿ€โ€ //! + `TargetMessage` either `inline_message_id: String` or `chat_id: ChatId` and `message_id: i64` //! * Integers represented with more strict (when possible) types, e.g.: -//! `u8` (unsigned, 8-bit integer), `u32` (unsigned, 32-bit), +//! `u8` (unsigned, 8-bit integer), `u32` (unsigned, 32-bit), //! `i64` (signed, 64-bit), etc //! * Instead of optional parameters `Option(Ty)` is used //! * Instead of `InputFile or String` just `InputFile` is used (assuming that //! `InputFile` is a sum-type or something and it can contain `String`s) //! * `f64` ~= `Float number` -//! +//! //! [tbadoc]: https://core.telegram.org/bots/api //! [RON]: https://github.com/ron-rs/ron //! [github]: https://github.com/WaffleLapkin/tg-methods-schema Schema( - api_version: ApiVersion(ver: "6.9", date: "September 22, 2023"), + api_version: ApiVersion(ver: "7.2", date: "March 31, 2024"), methods: [ Method( names: ("getUpdates", "GetUpdates", "get_updates"), @@ -49,7 +49,7 @@ Schema( md_links: { "wiki": "https://en.wikipedia.org/wiki/Push_technology#Long_polling", "Update": "https://core.telegram.org/bots/api#update", - }, + }, ), tg_doc: "https://core.telegram.org/bots/api#getupdates", tg_category: "Getting updates", @@ -155,7 +155,7 @@ Schema( md_links: {"amazing guide to Webhooks": "https://core.telegram.org/bots/webhooks"} ) ] - ), + ), Method( names: ("deleteWebhook", "DeleteWebhook", "delete_webhook"), return_ty: True, @@ -205,7 +205,7 @@ Schema( tg_doc: "https://core.telegram.org/bots/api#logout", tg_category: "Available methods", params: [], - ), + ), Method( names: ("close", "Close", "close"), return_ty: True, @@ -224,6 +224,11 @@ Schema( tg_doc: "https://core.telegram.org/bots/api#sendmessage", tg_category: "Available methods", params: [ + Param( + name: "business_connection_id", + ty: Option(RawTy("BusinessConnectionId")), + descr: Doc(md: "Unique identifier of the business connection on behalf of which the message will be sent"), + ), Param( name: "chat_id", ty: RawTy("Recipient"), @@ -253,13 +258,13 @@ Schema( descr: Doc(md: "List of special entities that appear in the message text, which can be specified instead of _parse\\_mode_"), ), Param( - name: "disable_web_page_preview", - ty: Option(bool), - descr: Doc(md: "Disables link previews for links in this message") + name: "link_preview_options", + ty: Option(RawTy("LinkPreviewOptions")), + descr: Doc(md: "Link preview generation options for the message"), ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"} @@ -271,20 +276,15 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", @@ -320,7 +320,7 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"} @@ -333,11 +333,56 @@ Schema( ), Param( name: "message_id", - ty: RawTy("MessageId"), + ty: RawTy("MessageId"), descr: Doc(md: "Message identifier in the chat specified in _from\\_chat\\_id_") ), ], ), + Method( + names: ("forwardMessages", "ForwardMessages", "forward_messages"), + return_ty: ArrayOf(RawTy("MessageId")), + doc: Doc( + md: "Use this method to forward multiple messages of any kind. If some of the specified messages can't be found or forwarded, they are skipped. Service messages and messages with protected content can't be forwarded. Album grouping is kept for forwarded messages. On success, an array of [MessageId] of the sent messages is returned.", + md_links: {"MessageId": "https://core.telegram.org/bots/api#messageid"}, + ), + tg_doc: "https://core.telegram.org/bots/api#forwardmessages", + tg_category: "Available methods", + params: [ + Param( + name: "chat_id", + ty: RawTy("Recipient"), + descr: Doc(md: "Unique identifier for the target chat or username of the target channel (in the format `@channelusername`)"), + ), + Param( + name: "message_thread_id", + ty: Option(RawTy("ThreadId")), + descr: Doc(md: "Unique identifier for the target message thread (topic) of the forum; for forum supergroups only"), + ), + Param( + name: "from_chat_id", + ty: RawTy("Recipient"), + descr: Doc(md: "Unique identifier for the chat where the original message was sent (or channel username in the format `@channelusername`)"), + ), + Param( + name: "message_ids", + ty: ArrayOf(RawTy("MessageId")), + descr: Doc(md: "A JSON-serialized list of 1-100 identifiers of messages in the chat _from\\_chat\\_id_ to forward. The identifiers must be specified in a strictly increasing order.") + ), + Param( + name: "disable_notification", + ty: Option(bool), + descr: Doc( + md: "Sends the message [silently]. Users will receive a notification with no sound.", + md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"} + ) + ), + Param( + name: "protect_content", + ty: Option(bool), + descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), + ), + ], + ), Method( names: ("copyMessage", "CopyMessage", "copy_message"), return_ty: RawTy("MessageId"), @@ -365,7 +410,7 @@ Schema( ), Param( name: "message_id", - ty: RawTy("MessageId"), + ty: RawTy("MessageId"), descr: Doc(md: "Message identifier in the chat specified in _from\\_chat\\_id_") ), Param( @@ -388,7 +433,7 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"} @@ -400,20 +445,15 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", @@ -422,6 +462,59 @@ Schema( ), ], ), + Method( + names: ("copyMessages", "CopyMessages", "copy_messages"), + return_ty: ArrayOf(RawTy("MessageId")), + doc: Doc( + md: "Use this method to copy messages of any kind. If some of the specified messages can't be found or copied, they are skipped. Service messages, giveaway messages, giveaway winners messages, and invoice messages can't be copied. A quiz poll can be copied only if the value of the field _correct\\_option\\_id_ is known to the bot. The method is analogous to the method [forwardMessages], but the copied messages don't have a link to the original message. Album grouping is kept for copied messages. On success, an array of [MessageId] of the sent messages is returned.", + md_links: { + "forwardMessages": "https://core.telegram.org/bots/api#forwardmessages", + "MessageId": "https://core.telegram.org/bots/api#messageid" + }, + ), + tg_doc: "https://core.telegram.org/bots/api#copymessages", + tg_category: "Available methods", + params: [ + Param( + name: "chat_id", + ty: RawTy("Recipient"), + descr: Doc(md: "Unique identifier for the target chat or username of the target channel (in the format `@channelusername`)"), + ), + Param( + name: "message_thread_id", + ty: Option(RawTy("ThreadId")), + descr: Doc(md: "Unique identifier for the target message thread (topic) of the forum; for forum supergroups only"), + ), + Param( + name: "from_chat_id", + ty: RawTy("Recipient"), + descr: Doc(md: "Unique identifier for the chat where the original message was sent (or channel username in the format `@channelusername`)"), + ), + Param( + name: "message_ids", + ty: ArrayOf(RawTy("MessageId")), + descr: Doc(md: "Identifiers of 1-100 messages in the chat _from\\_chat\\_id_ to copy. The identifiers must be specified in a strictly increasing order.") + ), + Param( + name: "disable_notification", + ty: Option(bool), + descr: Doc( + md: "Sends the message [silently]. Users will receive a notification with no sound.", + md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"} + ) + ), + Param( + name: "protect_content", + ty: Option(bool), + descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), + ), + Param( + name: "remove_caption", + ty: Option(bool), + descr: Doc(md: "Pass _True_ to copy the messages without their captions") + ) + ], + ), Method( names: ("sendPhoto", "SendPhoto", "send_photo"), return_ty: RawTy("Message"), @@ -432,6 +525,11 @@ Schema( tg_doc: "https://core.telegram.org/bots/api#sendphoto", tg_category: "Available methods", params: [ + Param( + name: "business_connection_id", + ty: Option(RawTy("BusinessConnectionId")), + descr: Doc(md: "Unique identifier of the business connection on behalf of which the message will be sent"), + ), Param( name: "chat_id", ty: RawTy("Recipient"), @@ -467,7 +565,7 @@ Schema( name: "caption_entities", ty: Option(ArrayOf(RawTy("MessageEntity"))), descr: Doc(md: "List of special entities that appear in the photo caption, which can be specified instead of _parse\\_mode_"), - ), + ), Param( name: "has_spoiler", ty: Option(bool), @@ -475,7 +573,7 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"} @@ -487,20 +585,15 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", @@ -522,6 +615,11 @@ Schema( tg_doc: "https://core.telegram.org/bots/api#sendaudio", tg_category: "Available methods", params: [ + Param( + name: "business_connection_id", + ty: Option(RawTy("BusinessConnectionId")), + descr: Doc(md: "Unique identifier of the business connection on behalf of which the message will be sent"), + ), Param( name: "chat_id", ty: RawTy("Recipient"), @@ -583,7 +681,7 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"}, @@ -595,20 +693,15 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", @@ -627,6 +720,11 @@ Schema( tg_doc: "https://core.telegram.org/bots/api#senddocument", tg_category: "Available methods", params: [ + Param( + name: "business_connection_id", + ty: Option(RawTy("BusinessConnectionId")), + descr: Doc(md: "Unique identifier of the business connection on behalf of which the message will be sent"), + ), Param( name: "chat_id", ty: RawTy("Recipient"), @@ -678,7 +776,7 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"}, @@ -690,20 +788,15 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", @@ -725,6 +818,11 @@ Schema( tg_doc: "https://core.telegram.org/bots/api#sendvideo", tg_category: "Available methods", params: [ + Param( + name: "business_connection_id", + ty: Option(RawTy("BusinessConnectionId")), + descr: Doc(md: "Unique identifier of the business connection on behalf of which the message will be sent"), + ), Param( name: "chat_id", ty: RawTy("Recipient"), @@ -796,7 +894,7 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"}, @@ -808,26 +906,21 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", } ), - + ), ], ), @@ -841,6 +934,11 @@ Schema( tg_doc: "https://core.telegram.org/bots/api#sendanimation", tg_category: "Available methods", params: [ + Param( + name: "business_connection_id", + ty: Option(RawTy("BusinessConnectionId")), + descr: Doc(md: "Unique identifier of the business connection on behalf of which the message will be sent"), + ), Param( name: "chat_id", ty: RawTy("Recipient"), @@ -907,7 +1005,7 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"}, @@ -919,20 +1017,15 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", @@ -955,6 +1048,11 @@ Schema( tg_doc: "https://core.telegram.org/bots/api#sendvoice", tg_category: "Available methods", params: [ + Param( + name: "business_connection_id", + ty: Option(RawTy("BusinessConnectionId")), + descr: Doc(md: "Unique identifier of the business connection on behalf of which the message will be sent"), + ), Param( name: "chat_id", ty: RawTy("Recipient"), @@ -998,31 +1096,26 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"}, ) ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", - } + } ), ), ], @@ -1040,6 +1133,11 @@ Schema( tg_doc: "https://core.telegram.org/bots/api#sendvideonote", tg_category: "Available methods", params: [ + Param( + name: "business_connection_id", + ty: Option(RawTy("BusinessConnectionId")), + descr: Doc(md: "Unique identifier of the business connection on behalf of which the message will be sent"), + ), Param( name: "chat_id", ty: RawTy("Recipient"), @@ -1078,7 +1176,7 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"}, @@ -1090,26 +1188,21 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", - } + } ), - + ), ], ), @@ -1123,6 +1216,11 @@ Schema( tg_doc: "https://core.telegram.org/bots/api#sendmediagroup", tg_category: "Available methods", params: [ + Param( + name: "business_connection_id", + ty: Option(RawTy("BusinessConnectionId")), + descr: Doc(md: "Unique identifier of the business connection on behalf of which the message will be sent"), + ), Param( name: "chat_id", ty: RawTy("Recipient"), @@ -1140,7 +1238,7 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"} @@ -1152,14 +1250,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), ], ), @@ -1173,6 +1266,11 @@ Schema( tg_doc: "https://core.telegram.org/bots/api#sendlocation", tg_category: "Available methods", params: [ + Param( + name: "business_connection_id", + ty: Option(RawTy("BusinessConnectionId")), + descr: Doc(md: "Unique identifier of the business connection on behalf of which the message will be sent"), + ), Param( name: "chat_id", ty: RawTy("Recipient"), @@ -1208,17 +1306,17 @@ Schema( ), Param( name: "heading", - ty: Option(u16), + ty: Option(u16), descr: Doc(md: "For live locations, a direction in which the user is moving, in degrees. Must be between 1 and 360 if specified.") ), Param( name: "proximity_alert_radius", - ty: Option(u32), + ty: Option(u32), descr: Doc(md: "For live locations, a maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified.") ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"} @@ -1230,20 +1328,15 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", @@ -1269,7 +1362,7 @@ Schema( ), Param( name: "message_id", - ty: RawTy("MessageId"), + ty: RawTy("MessageId"), descr: Doc(md: "Identifier of the message to edit") ), Param( @@ -1289,19 +1382,19 @@ Schema( ), Param( name: "heading", - ty: Option(u16), + ty: Option(u16), descr: Doc(md: "For live locations, a direction in which the user is moving, in degrees. Must be between 1 and 360 if specified.") ), Param( name: "proximity_alert_radius", - ty: Option(u32), + ty: Option(u32), descr: Doc(md: "For live locations, a maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified.") ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", @@ -1343,19 +1436,19 @@ Schema( ), Param( name: "heading", - ty: Option(u16), + ty: Option(u16), descr: Doc(md: "For live locations, a direction in which the user is moving, in degrees. Must be between 1 and 360 if specified.") ), Param( name: "proximity_alert_radius", - ty: Option(u32), + ty: Option(u32), descr: Doc(md: "For live locations, a maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified.") ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", @@ -1385,18 +1478,18 @@ Schema( ), Param( name: "message_id", - ty: RawTy("MessageId"), + ty: RawTy("MessageId"), descr: Doc(md: "Identifier of the message to edit") ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", - } + } ), ), ], @@ -1421,7 +1514,7 @@ Schema( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", @@ -1441,6 +1534,11 @@ Schema( tg_doc: "https://core.telegram.org/bots/api#sendvenue", tg_category: "Available methods", params: [ + Param( + name: "business_connection_id", + ty: Option(RawTy("BusinessConnectionId")), + descr: Doc(md: "Unique identifier of the business connection on behalf of which the message will be sent"), + ), Param( name: "chat_id", ty: RawTy("Recipient"), @@ -1496,7 +1594,7 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"} @@ -1508,24 +1606,19 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", - } + } ), ), ], @@ -1540,6 +1633,11 @@ Schema( tg_doc: "https://core.telegram.org/bots/api#sendcontact", tg_category: "Available methods", params: [ + Param( + name: "business_connection_id", + ty: Option(RawTy("BusinessConnectionId")), + descr: Doc(md: "Unique identifier of the business connection on behalf of which the message will be sent"), + ), Param( name: "chat_id", ty: RawTy("Recipient"), @@ -1575,7 +1673,7 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"} @@ -1587,20 +1685,15 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", @@ -1613,12 +1706,17 @@ Schema( names: ("sendPoll", "SendPoll", "send_poll"), return_ty: RawTy("Message"), doc: Doc( - md: "Use this method to send phone contacts. On success, the sent [Message] is returned.", + md: "Use this method to send a native poll. On success, the sent [Message] is returned.", md_links: {"Message": "https://core.telegram.org/bots/api#message"}, ), tg_doc: "https://core.telegram.org/bots/api#sendpoll", tg_category: "Available methods", params: [ + Param( + name: "business_connection_id", + ty: Option(RawTy("BusinessConnectionId")), + descr: Doc(md: "Unique identifier of the business connection on behalf of which the message will be sent"), + ), Param( name: "chat_id", ty: RawTy("Recipient"), @@ -1694,7 +1792,7 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"} @@ -1706,24 +1804,19 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", - } + } ), ), ], @@ -1738,6 +1831,11 @@ Schema( tg_doc: "https://core.telegram.org/bots/api#senddice", tg_category: "Available methods", params: [ + Param( + name: "business_connection_id", + ty: Option(RawTy("BusinessConnectionId")), + descr: Doc(md: "Unique identifier of the business connection on behalf of which the message will be sent"), + ), Param( name: "chat_id", ty: RawTy("Recipient"), @@ -1755,7 +1853,7 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"} @@ -1767,20 +1865,15 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", @@ -1799,11 +1892,16 @@ Schema( tg_doc: "https://core.telegram.org/bots/api#sendchataction", tg_category: "Available methods", params: [ + Param( + name: "business_connection_id", + ty: Option(RawTy("BusinessConnectionId")), + descr: Doc(md: "Unique identifier of the business connection on behalf of which the action will be sent"), + ), Param( name: "chat_id", ty: RawTy("Recipient"), descr: Doc(md: "Unique identifier for the target chat or username of the target channel (in the format `@channelusername`)") - ), + ), Param( name: "action", ty: RawTy("ChatAction"), @@ -1828,6 +1926,37 @@ Schema( ), ], ), + Method( + names: ("setMessageReaction", "SetMessageReaction", "set_message_reaction"), + return_ty: True, + doc: Doc( + md: "Use this method to change the chosen reactions on a message. Service messages can't be reacted to. Automatically forwarded messages from a channel to its discussion group have the same available reactions as messages in the channel. Returns True on success." + ), + tg_doc: "https://core.telegram.org/bots/api#setmessagereaction", + tg_category: "Available methods", + params: [ + Param( + name: "chat_id", + ty: RawTy("Recipient"), + descr: Doc(md: "Unique identifier for the target chat or username of the target channel (in the format @channelusername)") + ), + Param( + name: "message_id", + ty: RawTy("MessageId"), + descr: Doc(md: "Identifier of the target message. If the message belongs to a media group, the reaction is set to the first non-deleted message in the group instead.") + ), + Param( + name: "reaction", + ty: Option(ArrayOf(RawTy("ReactionType"))), + descr: Doc(md: "New list of reaction types to set on the message. Currently, as non-premium users, bots can set up to one reaction per message. A custom emoji reaction can be used if it is either already present on the message or explicitly allowed by chat administrators.") + ), + Param( + name: "is_big", + ty: Option(bool), + descr: Doc(md: "Pass True to set the reaction with a big animation") + ), + ], + ), Method( names: ("getUserProfilePhotos", "GetUserProfilePhotos", "get_user_profile_photos"), return_ty: RawTy("UserProfilePhotos"), @@ -2044,17 +2173,17 @@ Schema( Param( name: "can_post_stories", ty: Option(bool), - descr: Doc(md: "Pass True, if the administrator can post stories in the channel, channels only") + descr: Doc(md: "Pass True, if the administrator can post stories to the chat") ), Param( name: "can_edit_stories", ty: Option(bool), - descr: Doc(md: "Pass True, if the administrator can edit stories posted by other users, channels only") + descr: Doc(md: "Pass True, if the administrator can edit stories posted by other users") ), Param( name: "can_delete_stories", ty: Option(bool), - descr: Doc(md: "Pass True, if the administrator can delete stories posted by other users, channels only") + descr: Doc(md: "Pass True, if the administrator can delete stories posted by other users") ), Param( name: "can_manage_video_chats", @@ -2135,7 +2264,7 @@ Schema( descr: Doc(md: "Unique identifier of the target sender chat") ), ], - ), + ), Method( names: ("unbanChatSenderChat", "UnbanChatSenderChat", "unban_chat_sender_chat"), return_ty: True, @@ -2192,7 +2321,7 @@ Schema( descr: Doc(md: "Unique identifier for the target chat or username of the target channel (in the format `@channelusername`)") ), ], - ), + ), Method( names: ("createChatInviteLink", "CreateChatInviteLink", "create_chat_invite_link"), return_ty: RawTy("ChatInviteLink"), @@ -2232,7 +2361,7 @@ Schema( descr: Doc(md: "True, if users joining the chat via the link need to be approved by chat administrators. If True, member_limit can't be specified") ), ], - ), + ), Method( names: ("editChatInviteLink", "EditChatInviteLink", "edit_chat_invite_link"), return_ty: String, @@ -2274,7 +2403,7 @@ Schema( descr: Doc(md: "True, if users joining the chat via the link need to be approved by chat administrators. If True, member_limit can't be specified") ), ], - ), + ), Method( names: ("revokeChatInviteLink", "RevokeChatInviteLink", "revoke_chat_invite_link"), return_ty: String, @@ -2425,7 +2554,7 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc(md: "Pass True, if it is not necessary to send a notification to all chat members about the new pinned message. Notifications are always disabled in channels.") ), ], @@ -2625,9 +2754,11 @@ Schema( ), Param( name: "icon_color", - // FIXME: use an Rgb or something - ty: u32, - descr: Doc(md: "Color of the topic icon in RGB format. Currently, must be one of 7322096 (0x6FB9F0), 16766590 (0xFFD67E), 13338331 (0xCB86DB), 9367192 (0x8EEE98), 16749490 (0xFF93B2), or 16478047 (0xFB6F5F)") + ty: RawTy("Rgb"), + descr: Doc( + md: "Color of the topic icon in RGB format. Currently, must be one of 7322096 (`0x6FB9F0`), 16766590 (`0xFFD67E`), 13338331 (`0xCB86DB`), 9367192 (`0x8EEE98`), 16749490 (`0xFF93B2`), or 16478047 (`0xFB6F5F`). To construct color from these values use [`Rgb::from_u32`]", + md_links: {"`Rgb::from_u32`": "crate::types::Rgb::from_u32"} + ) ), Param( name: "icon_custom_emoji_id", @@ -2877,6 +3008,28 @@ Schema( ), ], ), + Method( + names: ("getUserChatBoosts", "GetUserChatBoosts", "get_user_chat_boosts"), + return_ty: RawTy("UserChatBoosts"), + doc: Doc( + md: "Use this method to get the list of boosts added to a chat by a user. Requires administrator rights in the chat. Returns a [UserChatBoosts] object.", + md_links: {"UserChatBoosts": "https://core.telegram.org/bots/api#userchatboosts"}, + ), + tg_doc: "https://core.telegram.org/bots/api#getuserchatboosts", + tg_category: "Available methods", + params: [ + Param( + name: "chat_id", + ty: RawTy("Recipient"), + descr: Doc(md: "Unique identifier for the chat or username of the channel (in the format @channelusername)"), + ), + Param( + name: "user_id", + ty: RawTy("UserId"), + descr: Doc(md: "Unique identifier of the target user"), + ), + ], + ), Method( names: ("setMyCommands", "SetMyCommands", "set_my_commands"), return_ty: True, @@ -2900,7 +3053,21 @@ Schema( descr: Doc(md: "A two-letter ISO 639-1 language code. If empty, commands will be applied to all users from the given scope, for whose language there are no dedicated commands") ), ], - ), + ), + Method( + names: ("getBusinessConnection", "GetBusinessConnection", "get_business_connection"), + return_ty: RawTy("BusinessConnection"), + doc: Doc(md: "Use this method to get information about the connection of the bot with a business account. Returns a BusinessConnection object on success."), + tg_doc: "https://core.telegram.org/bots/api#getbusinessconnection", + tg_category: "Available methods", + params: [ + Param( + name: "business_connection_id", + ty: RawTy("BusinessConnectionId"), + descr: Doc(md: "Unique identifier of the business connection"), + ), + ], + ), Method( names: ("getMyCommands", "GetMyCommands", "get_my_commands"), return_ty: ArrayOf(RawTy("BotCommand")), @@ -3162,7 +3329,7 @@ Schema( names: ("answerWebAppQuery", "AnswerWebAppQuery", "answer_web_app_query"), return_ty: RawTy("SentWebAppMessage"), doc: Doc( - md: "Use this method to set the result of an interaction with a [Web App] and send a corresponding message on behalf of the user to the chat from which the query originated.", + md: "Use this method to set the result of an interaction with a [Web App] and send a corresponding message on behalf of the user to the chat from which the query originated.", md_links: {"Web App": "https://core.telegram.org/bots/webapps"} ), tg_doc: "https://core.telegram.org/bots/api#answerwebappquery", @@ -3220,16 +3387,16 @@ Schema( descr: Doc(md: "List of special entities that appear in message text, which can be specified instead of _parse\\_mode_"), ), Param( - name: "disable_web_page_preview", - ty: Option(bool), - descr: Doc(md: "Disables link previews for links in this message") + name: "link_preview_options", + ty: Option(RawTy("LinkPreviewOptions")), + descr: Doc(md: "Link preview generation options for the message"), ), Param( name: "reply_markup", ty: Option(RawTy("InlineKeyboardMarkup")), descr: Doc( md: "A JSON-serialized object for an [inline keyboard].", - md_links: {"inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating"}, + md_links: {"inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating"}, ), ), ], @@ -3323,7 +3490,7 @@ Schema( name: "reply_markup", ty: Option(RawTy("InlineKeyboardMarkup")), descr: Doc( - md: "A JSON-serialized object for an [inline keyboard].", + md: "A JSON-serialized object for an [inline keyboard].", md_links: {"inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating",} ), ), @@ -3366,7 +3533,7 @@ Schema( ty: Option(RawTy("InlineKeyboardMarkup")), descr: Doc( md: "A JSON-serialized object for an [inline keyboard].", - md_links: {"inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating"} + md_links: {"inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating"} ), ), ], @@ -3526,19 +3693,46 @@ Schema( name: "message_id", ty: RawTy("MessageId"), descr: Doc(md: "Identifier of the message to delete") - ), + ), + ], + ), + Method( + names: ("deleteMessages", "DeleteMessages", "delete_messages"), + return_ty: True, + doc: Doc(md: "Use this method to delete multiple messages simultaneously. If some of the specified messages can't be found, they are skipped. Returns _True_ on success."), + tg_doc: "https://core.telegram.org/bots/api#delete_messages", + tg_category: "Updating messages", + params: [ + Param( + name: "chat_id", + ty: RawTy("Recipient"), + descr: Doc(md: "Unique identifier for the target chat or username of the target channel (in the format `@channelusername`).") + ), + Param( + name: "message_ids", + ty: ArrayOf(RawTy("MessageId")), + descr: Doc( + md: "Identifiers of 1-100 messages to delete. See [deleteMessage] for limitations on which messages can be deleted", + md_links: {"deleteMessage": "https://core.telegram.org/bots/api#delete_message"} + ) + ), ], ), Method( names: ("sendSticker", "SendSticker", "send_sticker"), return_ty: RawTy("Message"), doc: Doc( - md: "Use this method to send static .WEBP or [animated] .TGS stickers. On success, the sent Message is returned.", + md: "Use this method to send static .WEBP, .TGS or .WEBM stickers. On success, the sent Message is returned.", md_links: {"animated": "https://telegram.org/blog/animated-stickers"} ), tg_doc: "https://core.telegram.org/bots/api#sendsticker", tg_category: "Stickers", params: [ + Param( + name: "business_connection_id", + ty: Option(RawTy("BusinessConnectionId")), + descr: Doc(md: "Unique identifier of the business connection on behalf of which the message will be sent"), + ), Param( name: "chat_id", ty: RawTy("Recipient"), @@ -3564,7 +3758,7 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"} @@ -3576,20 +3770,15 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user.", + md: "Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account.", md_links: { "inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating", "custom reply keyboard": "https://core.telegram.org/bots#keyboards", @@ -3630,10 +3819,11 @@ Schema( names: ("uploadStickerFile", "UploadStickerFile", "upload_sticker_file"), return_ty: RawTy("FileMeta"), doc: Doc( - md: "Use this method to upload a file with a sticker for later use in the [CreateNewStickerSet] and [AddStickerToSet] methods (the file can be used multiple times). Returns the uploaded [File] on success.", + md: "Use this method to upload a file with a sticker for later use in the [CreateNewStickerSet], [AddStickerToSet] or [ReplaceStickerInSet] methods (the file can be used multiple times). Returns the uploaded [File] on success.", md_links: { "CreateNewStickerSet": "https://docs.rs/teloxide/latest/teloxide/payloads/struct.CreateNewStickerSet.html", "AddStickerToSet": "https://docs.rs/teloxide/latest/teloxide/payloads/struct.AddStickerToSet.html", + "ReplaceStickerInSet": "https://docs.rs/teloxide/latest/teloxide/payloads/struct.ReplaceStickerInSet.html", "File": "https://core.telegram.org/bots/api#file" } ), @@ -3690,11 +3880,6 @@ Schema( ty: ArrayOf(RawTy("InputSticker")), descr: Doc(md: "A JSON-serialized list of 1-50 initial stickers to be added to the sticker set") ), - Param( - name: "sticker_format", - ty: RawTy("StickerFormat"), - descr: Doc(md: "Format of the sticker, must be one of โ€œstaticโ€, โ€œanimatedโ€, โ€œvideoโ€") - ), Param( name: "sticker_type", ty: Option(RawTy("StickerType")), @@ -3766,6 +3951,42 @@ Schema( ), ], ), + Method( + names: ("replaceStickerInSet", "ReplaceStickerInSet", "replace_sticker_in_set"), + return_ty: True, + doc: Doc( + md: "Use this method to replace an existing sticker in a sticker set with a new one. The method is equivalent to calling [DeleteStickerFromSet], then [AddStickerToSet], then [SetStickerPositionInSet]. Returns _True_ on success.", + md_links: { + "DeleteStickerFromSet": "https://docs.rs/teloxide/latest/teloxide/payloads/struct.DeleteStickerFromSet.html", + "AddStickerToSet": "https://docs.rs/teloxide/latest/teloxide/payloads/struct.AddStickerToSet.html", + "SetStickerPositionInSet": "https://docs.rs/teloxide/latest/teloxide/payloads/struct.SetStickerPositionInSet.html", + } + ), + tg_doc: "https://core.telegram.org/bots/api#replacestickerinset", + tg_category: "Stickers", + params: [ + Param( + name: "user_id", + ty: RawTy("UserId"), + descr: Doc(md: "User identifier of the sticker set owner"), + ), + Param( + name: "name", + ty: String, + descr: Doc(md: "Sticker set name"), + ), + Param( + name: "old_sticker", + ty: String, + descr: Doc(md: "File identifier of the replaced sticker"), + ), + Param( + name: "sticker", + ty: RawTy("InputSticker"), + descr: Doc(md: "A JSON-serialized object with information about the added sticker. If exactly the same sticker had already been added to the set, then the set remains unchanged."), + ), + ], + ), Method( names: ("setStickerSetThumbnail", "SetStickerSetThumbnail", "set_sticker_set_thumbnail"), return_ty: True, @@ -3791,6 +4012,11 @@ Schema( md_links: {"More info on Sending Files ยป": "https://core.telegram.org/bots/api#sending-files"}, ), ), + Param( + name: "format", + ty: RawTy("StickerFormat"), + descr: Doc(md: "Format of the thumbnail, must be one of \"static\" for a .WEBP or .PNG image, \"animated\" for a .TGS animation, or \"video\" for a WEBM video") + ) ], ), Method( @@ -3942,13 +4168,19 @@ Schema( ty: String, descr: Doc( md: "Payments provider token, obtained via [Botfather]", - md_links: {"Botfather":"https://t.me/botfather"} + md_links: {"Botfather":"https://t.me/botfather"} ), ), Param( name: "currency", ty: String, - descr: Doc(md: "Three-letter ISO 4217 currency code, see more on currencies"), + descr: Doc( + md: "Three-letter ISO 4217 currency code, see [more on currencies]. Pass `XTR` for payments in [Telegram Stars].", + md_links: { + "more on currencies":"https://core.telegram.org/bots/payments#supported-currencies", + "Telegram Stars": "https://t.me/BotNews/90" + } + ), ), Param( name: "prices", @@ -4035,7 +4267,7 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"} @@ -4047,14 +4279,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", @@ -4093,13 +4320,19 @@ Schema( ty: String, descr: Doc( md: "Payments provider token, obtained via [Botfather]", - md_links: {"Botfather":"https://t.me/botfather"} + md_links: {"Botfather":"https://t.me/botfather"} ), ), Param( name: "currency", ty: String, - descr: Doc(md: "Three-letter ISO 4217 currency code, see more on currencies"), + descr: Doc( + md: "Three-letter ISO 4217 currency code, see [more on currencies]. Pass `XTR` for payments in [Telegram Stars].", + md_links: { + "more on currencies":"https://core.telegram.org/bots/payments#supported-currencies", + "Telegram Stars": "https://t.me/BotNews/90" + } + ), ), Param( name: "prices", @@ -4264,11 +4497,16 @@ Schema( return_ty: RawTy("Message"), doc: Doc( md: "Use this method to send a game. On success, the sent [Message] is returned.", - md_links: {"Message": "https://core.telegram.org/bots/api#message"} + md_links: {"Message": "https://core.telegram.org/bots/api#message"} ), tg_doc: "https://core.telegram.org/bots/api#sendgame", tg_category: "Games", params: [ + Param( + name: "business_connection_id", + ty: Option(RawTy("BusinessConnectionId")), + descr: Doc(md: "Unique identifier of the business connection on behalf of which the message will be sent"), + ), Param( name: "chat_id", ty: RawTy("ChatId"), @@ -4286,7 +4524,7 @@ Schema( ), Param( name: "disable_notification", - ty: Option(bool), + ty: Option(bool), descr: Doc( md: "Sends the message [silently]. Users will receive a notification with no sound.", md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"} @@ -4298,20 +4536,15 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", ty: Option(RawTy("ReplyMarkup")), descr: Doc( - md: "A JSON-serialized object for an [inline keyboard]. If empty, one 'Play game_title' button will be shown. If not empty, the first button must launch the game.", + md: "A JSON-serialized object for an [inline keyboard]. If empty, one 'Play game_title' button will be shown. If not empty, the first button must launch the game. Not supported for messages sent on behalf of a business account.", md_links: {"inline keyboard": "https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating"}, ), ), @@ -4322,7 +4555,7 @@ Schema( return_ty: RawTy("Message"), doc: Doc( md: "Use this method to set the score of the specified user in a game. On success, returns the edited [Message]. Returns an error, if the new score is not greater than the user's current score in the chat and force is False.", - md_links: {"Message": "https://core.telegram.org/bots/api#message"} + md_links: {"Message": "https://core.telegram.org/bots/api#message"} ), tg_doc: "https://core.telegram.org/bots/api#setgamescore", tg_category: "Games", @@ -4354,7 +4587,7 @@ Schema( ), Param( name: "message_id", - ty: RawTy("MessageId"), + ty: RawTy("MessageId"), descr: Doc(md: "Identifier of the message to edit") ), ], @@ -4425,7 +4658,7 @@ Schema( "Updating messages": "https://core.telegram.org/bots/api#updating-messages", "Stickers": "https://core.telegram.org/bots/api#stickers", "Payments": "https://core.telegram.org/bots/api#payments", - "Telegram Passport": "https://core.telegram.org/bots/api#telegram-passport", + "Telegram Passport": "https://core.telegram.org/bots/api#telegram-passport", "Games": "https://core.telegram.org/bots/api#games", } ) diff --git a/crates/teloxide-core/src/adaptors/cache_me.rs b/crates/teloxide-core/src/adaptors/cache_me.rs index eecac8bc..251d7473 100644 --- a/crates/teloxide-core/src/adaptors/cache_me.rs +++ b/crates/teloxide-core/src/adaptors/cache_me.rs @@ -94,7 +94,9 @@ where delete_webhook, get_webhook_info, forward_message, + forward_messages, copy_message, + copy_messages, send_message, send_photo, send_audio, @@ -114,6 +116,7 @@ where send_poll, send_dice, send_chat_action, + set_message_reaction, get_user_profile_photos, get_file, kick_chat_member, @@ -158,7 +161,9 @@ where unhide_general_forum_topic, unpin_all_general_forum_topic_messages, answer_callback_query, + get_user_chat_boosts, set_my_commands, + get_business_connection, get_my_commands, set_my_name, get_my_name, @@ -183,6 +188,7 @@ where edit_message_reply_markup_inline, stop_poll, delete_message, + delete_messages, send_sticker, get_sticker_set, get_custom_emoji_stickers, @@ -191,6 +197,7 @@ where add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, + replace_sticker_in_set, set_sticker_set_thumbnail, set_custom_emoji_sticker_set_thumbnail, set_sticker_set_title, diff --git a/crates/teloxide-core/src/adaptors/erased.rs b/crates/teloxide-core/src/adaptors/erased.rs index 7b56d648..1e0728a4 100644 --- a/crates/teloxide-core/src/adaptors/erased.rs +++ b/crates/teloxide-core/src/adaptors/erased.rs @@ -170,6 +170,9 @@ macro_rules! fwd_erased { (@convert $m:ident, $arg:ident, emoji_list: $T:ty) => { $arg.into_iter().collect() }; + (@convert $m:ident, $arg:ident, message_ids: $T:ty) => { + $arg.into_iter().collect() + }; (@convert $m:ident, $arg:ident, $arg_:ident : $T:ty) => { $arg.into() }; @@ -190,7 +193,9 @@ where delete_webhook, get_webhook_info, forward_message, + forward_messages, copy_message, + copy_messages, send_message, send_photo, send_audio, @@ -210,6 +215,7 @@ where send_poll, send_dice, send_chat_action, + set_message_reaction, get_user_profile_photos, get_file, kick_chat_member, @@ -254,7 +260,9 @@ where unhide_general_forum_topic, unpin_all_general_forum_topic_messages, answer_callback_query, + get_user_chat_boosts, set_my_commands, + get_business_connection, get_my_commands, set_my_name, get_my_name, @@ -279,6 +287,7 @@ where edit_message_reply_markup_inline, stop_poll, delete_message, + delete_messages, send_sticker, get_sticker_set, get_custom_emoji_stickers, @@ -287,6 +296,7 @@ where add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, + replace_sticker_in_set, set_sticker_set_thumbnail, set_custom_emoji_sticker_set_thumbnail, set_sticker_set_title, @@ -341,6 +351,13 @@ trait ErasableRequester<'a> { message_id: MessageId, ) -> ErasedRequest<'a, ForwardMessage, Self::Err>; + fn forward_messages( + &self, + chat_id: Recipient, + from_chat_id: Recipient, + message_ids: Vec, + ) -> ErasedRequest<'a, ForwardMessages, Self::Err>; + fn copy_message( &self, chat_id: Recipient, @@ -348,6 +365,13 @@ trait ErasableRequester<'a> { message_id: MessageId, ) -> ErasedRequest<'a, CopyMessage, Self::Err>; + fn copy_messages( + &self, + chat_id: Recipient, + from_chat_id: Recipient, + message_ids: Vec, + ) -> ErasedRequest<'a, CopyMessages, Self::Err>; + fn send_photo( &self, chat_id: Recipient, @@ -460,6 +484,12 @@ trait ErasableRequester<'a> { action: ChatAction, ) -> ErasedRequest<'a, SendChatAction, Self::Err>; + fn set_message_reaction( + &self, + chat_id: Recipient, + message_id: MessageId, + ) -> ErasedRequest<'a, SetMessageReaction, Self::Err>; + fn get_user_profile_photos( &self, user_id: UserId, @@ -641,7 +671,7 @@ trait ErasableRequester<'a> { &self, chat_id: Recipient, name: String, - icon_color: u32, + icon_color: Rgb, icon_custom_emoji_id: String, ) -> ErasedRequest<'a, CreateForumTopic, Self::Err>; @@ -711,11 +741,22 @@ trait ErasableRequester<'a> { callback_query_id: String, ) -> ErasedRequest<'a, AnswerCallbackQuery, Self::Err>; + fn get_user_chat_boosts( + &self, + chat_id: Recipient, + user_id: UserId, + ) -> ErasedRequest<'a, GetUserChatBoosts, Self::Err>; + fn set_my_commands( &self, commands: Vec, ) -> ErasedRequest<'a, SetMyCommands, Self::Err>; + fn get_business_connection( + &self, + business_connection_id: BusinessConnectionId, + ) -> ErasedRequest<'a, GetBusinessConnection, Self::Err>; + fn get_my_commands(&self) -> ErasedRequest<'a, GetMyCommands, Self::Err>; fn set_my_name(&self) -> ErasedRequest<'a, SetMyName, Self::Err>; @@ -816,6 +857,12 @@ trait ErasableRequester<'a> { message_id: MessageId, ) -> ErasedRequest<'a, DeleteMessage, Self::Err>; + fn delete_messages( + &self, + chat_id: Recipient, + message_ids: Vec, + ) -> ErasedRequest<'a, DeleteMessages, Self::Err>; + fn send_sticker( &self, chat_id: Recipient, @@ -842,7 +889,6 @@ trait ErasableRequester<'a> { name: String, title: String, stickers: Vec, - sticker_format: StickerFormat, ) -> ErasedRequest<'a, CreateNewStickerSet, Self::Err>; fn add_sticker_to_set( @@ -863,10 +909,19 @@ trait ErasableRequester<'a> { sticker: String, ) -> ErasedRequest<'a, DeleteStickerFromSet, Self::Err>; + fn replace_sticker_in_set( + &self, + user_id: UserId, + name: String, + old_sticker: String, + sticker: InputSticker, + ) -> ErasedRequest<'a, ReplaceStickerInSet, Self::Err>; + fn set_sticker_set_thumbnail( &self, name: String, user_id: UserId, + format: StickerFormat, ) -> ErasedRequest<'a, SetStickerSetThumbnail, Self::Err>; fn set_custom_emoji_sticker_set_thumbnail( @@ -1019,6 +1074,15 @@ where Requester::forward_message(self, chat_id, from_chat_id, message_id).erase() } + fn forward_messages( + &self, + chat_id: Recipient, + from_chat_id: Recipient, + message_ids: Vec, + ) -> ErasedRequest<'a, ForwardMessages, Self::Err> { + Requester::forward_messages(self, chat_id, from_chat_id, message_ids).erase() + } + fn copy_message( &self, chat_id: Recipient, @@ -1028,6 +1092,15 @@ where Requester::copy_message(self, chat_id, from_chat_id, message_id).erase() } + fn copy_messages( + &self, + chat_id: Recipient, + from_chat_id: Recipient, + message_ids: Vec, + ) -> ErasedRequest<'a, CopyMessages, Self::Err> { + Requester::copy_messages(self, chat_id, from_chat_id, message_ids).erase() + } + fn send_photo( &self, chat_id: Recipient, @@ -1178,6 +1251,14 @@ where Requester::send_chat_action(self, chat_id, action).erase() } + fn set_message_reaction( + &self, + chat_id: Recipient, + message_id: MessageId, + ) -> ErasedRequest<'a, SetMessageReaction, Self::Err> { + Requester::set_message_reaction(self, chat_id, message_id).erase() + } + fn get_user_profile_photos( &self, user_id: UserId, @@ -1425,7 +1506,7 @@ where &self, chat_id: Recipient, name: String, - icon_color: u32, + icon_color: Rgb, icon_custom_emoji_id: String, ) -> ErasedRequest<'a, CreateForumTopic, Self::Err> { Requester::create_forum_topic(self, chat_id, name, icon_color, icon_custom_emoji_id).erase() @@ -1521,6 +1602,14 @@ where Requester::answer_callback_query(self, callback_query_id).erase() } + fn get_user_chat_boosts( + &self, + chat_id: Recipient, + user_id: UserId, + ) -> ErasedRequest<'a, GetUserChatBoosts, Self::Err> { + Requester::get_user_chat_boosts(self, chat_id, user_id).erase() + } + fn set_my_commands( &self, commands: Vec, @@ -1528,6 +1617,13 @@ where Requester::set_my_commands(self, commands).erase() } + fn get_business_connection( + &self, + business_connection_id: BusinessConnectionId, + ) -> ErasedRequest<'a, GetBusinessConnection, Self::Err> { + Requester::get_business_connection(self, business_connection_id).erase() + } + fn get_my_commands(&self) -> ErasedRequest<'a, GetMyCommands, Self::Err> { Requester::get_my_commands(self).erase() } @@ -1676,6 +1772,14 @@ where Requester::delete_message(self, chat_id, message_id).erase() } + fn delete_messages( + &self, + chat_id: Recipient, + message_ids: Vec, + ) -> ErasedRequest<'a, DeleteMessages, Self::Err> { + Requester::delete_messages(self, chat_id, message_ids).erase() + } + fn send_sticker( &self, chat_id: Recipient, @@ -1710,10 +1814,8 @@ where name: String, title: String, stickers: Vec, - sticker_format: StickerFormat, ) -> ErasedRequest<'a, CreateNewStickerSet, Self::Err> { - Requester::create_new_sticker_set(self, user_id, name, title, stickers, sticker_format) - .erase() + Requester::create_new_sticker_set(self, user_id, name, title, stickers).erase() } fn add_sticker_to_set( @@ -1740,12 +1842,23 @@ where Requester::delete_sticker_from_set(self, sticker).erase() } + fn replace_sticker_in_set( + &self, + user_id: UserId, + name: String, + old_sticker: String, + sticker: InputSticker, + ) -> ErasedRequest<'a, ReplaceStickerInSet, Self::Err> { + Requester::replace_sticker_in_set(self, user_id, name, old_sticker, sticker).erase() + } + fn set_sticker_set_thumbnail( &self, name: String, user_id: UserId, + format: StickerFormat, ) -> ErasedRequest<'a, SetStickerSetThumbnail, Self::Err> { - Requester::set_sticker_set_thumbnail(self, name, user_id).erase() + Requester::set_sticker_set_thumbnail(self, name, user_id, format).erase() } fn set_custom_emoji_sticker_set_thumbnail( diff --git a/crates/teloxide-core/src/adaptors/parse_mode.rs b/crates/teloxide-core/src/adaptors/parse_mode.rs index 216893a4..4bc3f6f4 100644 --- a/crates/teloxide-core/src/adaptors/parse_mode.rs +++ b/crates/teloxide-core/src/adaptors/parse_mode.rs @@ -185,6 +185,8 @@ where delete_webhook, get_webhook_info, forward_message, + forward_messages, + copy_messages, send_video_note, send_location, edit_message_live_location, @@ -195,6 +197,7 @@ where send_contact, send_dice, send_chat_action, + set_message_reaction, get_user_profile_photos, get_file, kick_chat_member, @@ -239,7 +242,9 @@ where unhide_general_forum_topic, unpin_all_general_forum_topic_messages, answer_callback_query, + get_user_chat_boosts, set_my_commands, + get_business_connection, get_my_commands, set_my_name, get_my_name, @@ -256,6 +261,7 @@ where edit_message_reply_markup_inline, stop_poll, delete_message, + delete_messages, send_sticker, get_sticker_set, get_custom_emoji_stickers, @@ -264,6 +270,7 @@ where add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, + replace_sticker_in_set, set_sticker_set_thumbnail, set_custom_emoji_sticker_set_thumbnail, set_sticker_set_title, diff --git a/crates/teloxide-core/src/adaptors/throttle/request.rs b/crates/teloxide-core/src/adaptors/throttle/request.rs index 47e7d451..26af4bd5 100644 --- a/crates/teloxide-core/src/adaptors/throttle/request.rs +++ b/crates/teloxide-core/src/adaptors/throttle/request.rs @@ -19,6 +19,7 @@ use crate::{ /// Request returned by [`Throttling`](crate::adaptors::Throttle) methods. #[must_use = "Requests are lazy and do nothing unless sent"] +#[derive(Clone)] pub struct ThrottlingRequest { pub(super) request: Arc, pub(super) chat_id: fn(&R::Payload) -> ChatIdHash, diff --git a/crates/teloxide-core/src/adaptors/throttle/requester_impl.rs b/crates/teloxide-core/src/adaptors/throttle/requester_impl.rs index 28d82051..e12e0de8 100644 --- a/crates/teloxide-core/src/adaptors/throttle/requester_impl.rs +++ b/crates/teloxide-core/src/adaptors/throttle/requester_impl.rs @@ -92,11 +92,14 @@ where set_webhook, delete_webhook, get_webhook_info, + forward_messages, + copy_messages, edit_message_live_location, edit_message_live_location_inline, stop_message_live_location, stop_message_live_location_inline, send_chat_action, + set_message_reaction, get_user_profile_photos, get_file, kick_chat_member, @@ -141,7 +144,9 @@ where unhide_general_forum_topic, unpin_all_general_forum_topic_messages, answer_callback_query, + get_user_chat_boosts, set_my_commands, + get_business_connection, get_my_commands, set_my_name, get_my_name, @@ -166,6 +171,7 @@ where edit_message_reply_markup_inline, stop_poll, delete_message, + delete_messages, get_sticker_set, get_custom_emoji_stickers, upload_sticker_file, @@ -173,6 +179,7 @@ where add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, + replace_sticker_in_set, set_sticker_set_thumbnail, set_custom_emoji_sticker_set_thumbnail, set_sticker_set_title, diff --git a/crates/teloxide-core/src/adaptors/trace.rs b/crates/teloxide-core/src/adaptors/trace.rs index bf4141bc..e675453d 100644 --- a/crates/teloxide-core/src/adaptors/trace.rs +++ b/crates/teloxide-core/src/adaptors/trace.rs @@ -123,7 +123,9 @@ where delete_webhook, get_webhook_info, forward_message, + forward_messages, copy_message, + copy_messages, send_message, send_photo, send_audio, @@ -143,6 +145,7 @@ where send_poll, send_dice, send_chat_action, + set_message_reaction, get_user_profile_photos, get_file, kick_chat_member, @@ -187,7 +190,9 @@ where unhide_general_forum_topic, unpin_all_general_forum_topic_messages, answer_callback_query, + get_user_chat_boosts, set_my_commands, + get_business_connection, get_my_commands, set_my_name, get_my_name, @@ -212,6 +217,7 @@ where edit_message_reply_markup_inline, stop_poll, delete_message, + delete_messages, send_sticker, get_sticker_set, get_custom_emoji_stickers, @@ -220,6 +226,7 @@ where add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, + replace_sticker_in_set, set_sticker_set_thumbnail, set_custom_emoji_sticker_set_thumbnail, set_sticker_set_title, @@ -243,6 +250,7 @@ where } #[must_use = "Requests are lazy and do nothing unless sent"] +#[derive(Clone)] pub struct TraceRequest { inner: R, settings: Settings, diff --git a/crates/teloxide-core/src/bot/api.rs b/crates/teloxide-core/src/bot/api.rs index 380d41a4..7fed7fc5 100644 --- a/crates/teloxide-core/src/bot/api.rs +++ b/crates/teloxide-core/src/bot/api.rs @@ -5,8 +5,9 @@ use crate::{ prelude::Requester, requests::{JsonRequest, MultipartRequest}, types::{ - BotCommand, ChatId, ChatPermissions, InlineQueryResult, InputFile, InputMedia, - InputSticker, LabeledPrice, MessageId, Recipient, StickerFormat, ThreadId, UserId, + BotCommand, BusinessConnectionId, ChatId, ChatPermissions, InlineQueryResult, InputFile, + InputMedia, InputSticker, LabeledPrice, MessageId, Recipient, Rgb, StickerFormat, ThreadId, + UserId, }, Bot, }; @@ -72,6 +73,24 @@ impl Requester for Bot { ) } + type ForwardMessages = JsonRequest; + fn forward_messages( + &self, + chat_id: C, + from_chat_id: F, + message_ids: M, + ) -> Self::ForwardMessages + where + C: Into, + F: Into, + M: IntoIterator, + { + Self::ForwardMessages::new( + self.clone(), + payloads::ForwardMessages::new(chat_id, from_chat_id, message_ids), + ) + } + type SendPhoto = MultipartRequest; fn send_photo(&self, chat_id: C, photo: InputFile) -> Self::SendPhoto @@ -291,6 +310,18 @@ impl Requester for Bot { Self::SendChatAction::new(self.clone(), payloads::SendChatAction::new(chat_id, action)) } + type SetMessageReaction = JsonRequest; + + fn set_message_reaction(&self, chat_id: C, message_id: MessageId) -> Self::SetMessageReaction + where + C: Into, + { + Self::SetMessageReaction::new( + self.clone(), + payloads::SetMessageReaction::new(chat_id, message_id), + ) + } + type GetUserProfilePhotos = JsonRequest; fn get_user_profile_photos(&self, user_id: UserId) -> Self::GetUserProfilePhotos { @@ -655,7 +686,7 @@ impl Requester for Bot { &self, chat_id: C, name: N, - icon_color: u32, + icon_color: Rgb, icon_custom_emoji_id: I, ) -> Self::CreateForumTopic where @@ -830,6 +861,18 @@ impl Requester for Bot { ) } + type GetUserChatBoosts = JsonRequest; + + fn get_user_chat_boosts(&self, chat_id: C, user_id: UserId) -> Self::GetUserChatBoosts + where + C: Into, + { + Self::GetUserChatBoosts::new( + self.clone(), + payloads::GetUserChatBoosts::new(chat_id, user_id), + ) + } + type SetMyCommands = JsonRequest; fn set_my_commands(&self, commands: C) -> Self::SetMyCommands @@ -839,6 +882,18 @@ impl Requester for Bot { Self::SetMyCommands::new(self.clone(), payloads::SetMyCommands::new(commands)) } + type GetBusinessConnection = JsonRequest; + + fn get_business_connection( + &self, + business_connection_id: BusinessConnectionId, + ) -> Self::GetBusinessConnection { + Self::GetBusinessConnection::new( + self.clone(), + payloads::GetBusinessConnection::new(business_connection_id), + ) + } + type GetMyCommands = JsonRequest; fn get_my_commands(&self) -> Self::GetMyCommands { @@ -1086,6 +1141,15 @@ impl Requester for Bot { Self::DeleteMessage::new(self.clone(), payloads::DeleteMessage::new(chat_id, message_id)) } + type DeleteMessages = JsonRequest; + fn delete_messages(&self, chat_id: C, message_ids: M) -> Self::DeleteMessages + where + C: Into, + M: IntoIterator, + { + Self::DeleteMessages::new(self.clone(), payloads::DeleteMessages::new(chat_id, message_ids)) + } + type SendSticker = MultipartRequest; fn send_sticker(&self, chat_id: C, sticker: InputFile) -> Self::SendSticker @@ -1122,7 +1186,7 @@ impl Requester for Bot { &self, user_id: UserId, sticker: InputFile, - sticker_format: crate::types::StickerFormat, + sticker_format: StickerFormat, ) -> Self::UploadStickerFile { Self::UploadStickerFile::new( self.clone(), @@ -1138,7 +1202,6 @@ impl Requester for Bot { name: N, title: T, stickers: S, - sticker_format: StickerFormat, ) -> Self::CreateNewStickerSet where N: Into, @@ -1147,7 +1210,7 @@ impl Requester for Bot { { Self::CreateNewStickerSet::new( self.clone(), - payloads::CreateNewStickerSet::new(user_id, name, title, stickers, sticker_format), + payloads::CreateNewStickerSet::new(user_id, name, title, stickers), ) } @@ -1193,15 +1256,44 @@ impl Requester for Bot { Self::DeleteStickerFromSet::new(self.clone(), payloads::DeleteStickerFromSet::new(sticker)) } + type ReplaceStickerInSet = JsonRequest; + + fn replace_sticker_in_set( + &self, + user_id: UserId, + name: N, + old_sticker: O, + sticker: InputSticker, + ) -> Self::ReplaceStickerInSet + where + N: Into, + O: Into, + { + Self::ReplaceStickerInSet::new( + self.clone(), + payloads::ReplaceStickerInSet { + user_id, + name: name.into(), + old_sticker: old_sticker.into(), + sticker, + }, + ) + } + type SetStickerSetThumbnail = MultipartRequest; - fn set_sticker_set_thumbnail(&self, name: N, user_id: UserId) -> Self::SetStickerSetThumbnail + fn set_sticker_set_thumbnail( + &self, + name: N, + user_id: UserId, + format: StickerFormat, + ) -> Self::SetStickerSetThumbnail where N: Into, { Self::SetStickerSetThumbnail::new( self.clone(), - payloads::SetStickerSetThumbnail::new(name, user_id), + payloads::SetStickerSetThumbnail::new(name, user_id, format), ) } @@ -1465,6 +1557,24 @@ impl Requester for Bot { ) } + type CopyMessages = JsonRequest; + fn copy_messages( + &self, + chat_id: C, + from_chat_id: F, + message_ids: M, + ) -> Self::CopyMessages + where + C: Into, + F: Into, + M: IntoIterator, + { + Self::CopyMessages::new( + self.clone(), + payloads::CopyMessages::new(chat_id, from_chat_id, message_ids), + ) + } + type UnpinAllChatMessages = JsonRequest; fn unpin_all_chat_messages(&self, chat_id: C) -> Self::UnpinAllChatMessages diff --git a/crates/teloxide-core/src/lib.rs b/crates/teloxide-core/src/lib.rs index a2b8f688..e3eab009 100644 --- a/crates/teloxide-core/src/lib.rs +++ b/crates/teloxide-core/src/lib.rs @@ -1,13 +1,13 @@ //! Core part of the [`teloxide`] library. //! //! This library provides tools for making requests to the [Telegram Bot API] -//! (Currently, version `6.9` is supported) with ease. The library is fully +//! (Currently, version `7.2` is supported) with ease. The library is fully //! asynchronous and built using [`tokio`]. //! //!```toml -//! teloxide-core = "0.9" +//! teloxide-core = "0.10.1" //! ``` -//! _Compiler support: requires rustc 1.70+_. +//! _Compiler support: requires rustc 1.80+_. //! //! ``` //! # async { diff --git a/crates/teloxide-core/src/local_macros.rs b/crates/teloxide-core/src/local_macros.rs index 93108978..a6d2abbf 100644 --- a/crates/teloxide-core/src/local_macros.rs +++ b/crates/teloxide-core/src/local_macros.rs @@ -487,6 +487,16 @@ macro_rules! requester_forward { $body!(forward_message this (chat_id: C, from_chat_id: F, message_id: MessageId)) } }; + (@method forward_messages $body:ident $ty:ident) => { + type ForwardMessages = $ty![ForwardMessages]; + + fn forward_messages(&self, chat_id: C, from_chat_id: F, message_ids: M) -> Self::ForwardMessages where C: Into, + F: Into, + M: IntoIterator { + let this = self; + $body!(forward_messages this (chat_id: C, from_chat_id: F, message_ids: M)) + } + }; (@method copy_message $body:ident $ty:ident) => { type CopyMessage = $ty![CopyMessage]; @@ -496,6 +506,16 @@ macro_rules! requester_forward { $body!(copy_message this (chat_id: C, from_chat_id: F, message_id: MessageId)) } }; + (@method copy_messages $body:ident $ty:ident) => { + type CopyMessages = $ty![CopyMessages]; + + fn copy_messages(&self, chat_id: C, from_chat_id: F, message_ids: M) -> Self::CopyMessages where C: Into, + F: Into, + M: IntoIterator { + let this = self; + $body!(copy_messages this (chat_id: C, from_chat_id: F, message_ids: M)) + } + }; (@method send_photo $body:ident $ty:ident) => { type SendPhoto = $ty![SendPhoto]; @@ -647,6 +667,14 @@ macro_rules! requester_forward { $body!(send_chat_action this (chat_id: C, action: ChatAction)) } }; + (@method set_message_reaction $body:ident $ty:ident) => { + type SetMessageReaction = $ty![SetMessageReaction]; + + fn set_message_reaction(&self, chat_id: C, message_id: MessageId) -> Self::SetMessageReaction where C: Into { + let this = self; + $body!(set_message_reaction this (chat_id: C, message_id: MessageId)) + } + }; (@method get_user_profile_photos $body:ident $ty:ident) => { type GetUserProfilePhotos = $ty![GetUserProfilePhotos]; @@ -921,11 +949,11 @@ macro_rules! requester_forward { (@method create_forum_topic $body:ident $ty:ident) => { type CreateForumTopic = $ty![CreateForumTopic]; - fn create_forum_topic(&self, chat_id: C, name: N, icon_color: u32, icon_custom_emoji_id: I) -> Self::CreateForumTopic where C: Into, + fn create_forum_topic(&self, chat_id: C, name: N, icon_color: Rgb, icon_custom_emoji_id: I) -> Self::CreateForumTopic where C: Into, N: Into, I: Into { let this = self; - $body!(create_forum_topic this (chat_id: C, name: N, icon_color: u32, icon_custom_emoji_id: I)) + $body!(create_forum_topic this (chat_id: C, name: N, icon_color: Rgb, icon_custom_emoji_id: I)) } }; (@method edit_forum_topic $body:ident $ty:ident) => { @@ -1025,6 +1053,14 @@ macro_rules! requester_forward { $body!(answer_callback_query this (callback_query_id: C)) } }; + (@method get_user_chat_boosts $body:ident $ty:ident) => { + type GetUserChatBoosts = $ty![GetUserChatBoosts]; + + fn get_user_chat_boosts(&self, chat_id: C, user_id: UserId) -> Self::GetUserChatBoosts where C: Into { + let this = self; + $body!(get_user_chat_boosts this (chat_id: C, user_id: UserId)) + } + }; (@method set_my_commands $body:ident $ty:ident) => { type SetMyCommands = $ty![SetMyCommands]; @@ -1033,6 +1069,14 @@ macro_rules! requester_forward { $body!(set_my_commands this (commands: C)) } }; + (@method get_business_connection $body:ident $ty:ident) => { + type GetBusinessConnection = $ty![GetBusinessConnection]; + + fn get_business_connection(&self, business_connection_id: BusinessConnectionId) -> Self::GetBusinessConnection { + let this = self; + $body!(get_business_connection this (business_connection_id: BusinessConnectionId)) + } + }; (@method get_my_commands $body:ident $ty:ident) => { type GetMyCommands = $ty![GetMyCommands]; @@ -1228,6 +1272,15 @@ macro_rules! requester_forward { $body!(delete_message this (chat_id: C, message_id: MessageId)) } }; + (@method delete_messages $body:ident $ty:ident) => { + type DeleteMessages = $ty![DeleteMessages]; + + fn delete_messages(&self, chat_id: C, message_ids: M) -> Self::DeleteMessages where C: Into, + M: IntoIterator { + let this = self; + $body!(delete_messages this (chat_id: C, message_ids: M)) + } + }; (@method send_sticker $body:ident $ty:ident) => { type SendSticker = $ty![SendSticker]; @@ -1263,11 +1316,11 @@ macro_rules! requester_forward { (@method create_new_sticker_set $body:ident $ty:ident) => { type CreateNewStickerSet = $ty![CreateNewStickerSet]; - fn create_new_sticker_set(&self, user_id: UserId, name: N, title: T, stickers: S, sticker_format: StickerFormat) -> Self::CreateNewStickerSet where N: Into, + fn create_new_sticker_set(&self, user_id: UserId, name: N, title: T, stickers: S) -> Self::CreateNewStickerSet where N: Into, T: Into, S: IntoIterator { let this = self; - $body!(create_new_sticker_set this (user_id: UserId, name: N, title: T, stickers: S, sticker_format: StickerFormat)) + $body!(create_new_sticker_set this (user_id: UserId, name: N, title: T, stickers: S)) } }; (@method add_sticker_to_set $body:ident $ty:ident) => { @@ -1294,12 +1347,21 @@ macro_rules! requester_forward { $body!(delete_sticker_from_set this (sticker: S)) } }; + (@method replace_sticker_in_set $body:ident $ty:ident) => { + type ReplaceStickerInSet = $ty![ReplaceStickerInSet]; + + fn replace_sticker_in_set(&self, user_id: UserId, name: N, old_sticker: O, sticker: InputSticker) -> Self::ReplaceStickerInSet where N: Into, + O: Into { + let this = self; + $body!(replace_sticker_in_set this (user_id: UserId, name: N, old_sticker: O, sticker: InputSticker)) + } + }; (@method set_sticker_set_thumbnail $body:ident $ty:ident) => { type SetStickerSetThumbnail = $ty![SetStickerSetThumbnail]; - fn set_sticker_set_thumbnail(&self, name: N, user_id: UserId) -> Self::SetStickerSetThumbnail where N: Into { + fn set_sticker_set_thumbnail(&self, name: N, user_id: UserId, format: StickerFormat) -> Self::SetStickerSetThumbnail where N: Into { let this = self; - $body!(set_sticker_set_thumbnail this (name: N, user_id: UserId)) + $body!(set_sticker_set_thumbnail this (name: N, user_id: UserId, format: StickerFormat)) } }; (@method set_custom_emoji_sticker_set_thumbnail $body:ident $ty:ident) => { @@ -1470,7 +1532,7 @@ fn codegen_requester_forward() { convert_params.sort_unstable(); - let prefixes: IndexMap<_, _> = convert_params + let mut prefixes: IndexMap<_, _> = convert_params .iter() .copied() // Workaround to output the last type as the first letter @@ -1479,6 +1541,18 @@ fn codegen_requester_forward() { .map(|(l, r)| (l, min_prefix(l, r))) .collect(); + // FIXME: This hard-coded value has been set to avoid conflicting generic + // parameter 'B' with impl Requester... in all the adaptors and other places + // + // One fix could be to take full abbrevation for all the parameters instead of + // just the first character. Other fix is to change the generic parameter name + // in all the impl blocks to something like 'Z' because that is very less likely + // to conflict in future. + if prefixes.contains_key("business_connection_id") { + prefixes["business_connection_id"] = "BCI"; + } + let prefixes = prefixes; + let args = m .params .iter() diff --git a/crates/teloxide-core/src/payloads.rs b/crates/teloxide-core/src/payloads.rs index 7f3cf308..34e52286 100644 --- a/crates/teloxide-core/src/payloads.rs +++ b/crates/teloxide-core/src/payloads.rs @@ -28,6 +28,7 @@ mod close; mod close_forum_topic; mod close_general_forum_topic; mod copy_message; +mod copy_messages; mod create_chat_invite_link; mod create_forum_topic; mod create_invoice_link; @@ -37,6 +38,7 @@ mod delete_chat_photo; mod delete_chat_sticker_set; mod delete_forum_topic; mod delete_message; +mod delete_messages; mod delete_my_commands; mod delete_sticker_from_set; mod delete_sticker_set; @@ -56,6 +58,8 @@ mod edit_message_text; mod edit_message_text_inline; mod export_chat_invite_link; mod forward_message; +mod forward_messages; +mod get_business_connection; mod get_chat; mod get_chat_administrators; mod get_chat_member; @@ -74,6 +78,7 @@ mod get_my_name; mod get_my_short_description; mod get_sticker_set; mod get_updates; +mod get_user_chat_boosts; mod get_user_profile_photos; mod get_webhook_info; mod hide_general_forum_topic; @@ -84,6 +89,7 @@ mod pin_chat_message; mod promote_chat_member; mod reopen_forum_topic; mod reopen_general_forum_topic; +mod replace_sticker_in_set; mod restrict_chat_member; mod revoke_chat_invite_link; mod send_animation; @@ -114,6 +120,7 @@ mod set_chat_title; mod set_custom_emoji_sticker_set_thumbnail; mod set_game_score; mod set_game_score_inline; +mod set_message_reaction; mod set_my_commands; mod set_my_default_administrator_rights; mod set_my_description; @@ -152,6 +159,7 @@ pub use close::{Close, CloseSetters}; pub use close_forum_topic::{CloseForumTopic, CloseForumTopicSetters}; pub use close_general_forum_topic::{CloseGeneralForumTopic, CloseGeneralForumTopicSetters}; pub use copy_message::{CopyMessage, CopyMessageSetters}; +pub use copy_messages::{CopyMessages, CopyMessagesSetters}; pub use create_chat_invite_link::{CreateChatInviteLink, CreateChatInviteLinkSetters}; pub use create_forum_topic::{CreateForumTopic, CreateForumTopicSetters}; pub use create_invoice_link::{CreateInvoiceLink, CreateInvoiceLinkSetters}; @@ -161,6 +169,7 @@ pub use delete_chat_photo::{DeleteChatPhoto, DeleteChatPhotoSetters}; pub use delete_chat_sticker_set::{DeleteChatStickerSet, DeleteChatStickerSetSetters}; pub use delete_forum_topic::{DeleteForumTopic, DeleteForumTopicSetters}; pub use delete_message::{DeleteMessage, DeleteMessageSetters}; +pub use delete_messages::{DeleteMessages, DeleteMessagesSetters}; pub use delete_my_commands::{DeleteMyCommands, DeleteMyCommandsSetters}; pub use delete_sticker_from_set::{DeleteStickerFromSet, DeleteStickerFromSetSetters}; pub use delete_sticker_set::{DeleteStickerSet, DeleteStickerSetSetters}; @@ -184,6 +193,8 @@ pub use edit_message_text::{EditMessageText, EditMessageTextSetters}; pub use edit_message_text_inline::{EditMessageTextInline, EditMessageTextInlineSetters}; pub use export_chat_invite_link::{ExportChatInviteLink, ExportChatInviteLinkSetters}; pub use forward_message::{ForwardMessage, ForwardMessageSetters}; +pub use forward_messages::{ForwardMessages, ForwardMessagesSetters}; +pub use get_business_connection::{GetBusinessConnection, GetBusinessConnectionSetters}; pub use get_chat::{GetChat, GetChatSetters}; pub use get_chat_administrators::{GetChatAdministrators, GetChatAdministratorsSetters}; pub use get_chat_member::{GetChatMember, GetChatMemberSetters}; @@ -206,6 +217,7 @@ pub use get_my_name::{GetMyName, GetMyNameSetters}; pub use get_my_short_description::{GetMyShortDescription, GetMyShortDescriptionSetters}; pub use get_sticker_set::{GetStickerSet, GetStickerSetSetters}; pub use get_updates::{GetUpdates, GetUpdatesSetters}; +pub use get_user_chat_boosts::{GetUserChatBoosts, GetUserChatBoostsSetters}; pub use get_user_profile_photos::{GetUserProfilePhotos, GetUserProfilePhotosSetters}; pub use get_webhook_info::{GetWebhookInfo, GetWebhookInfoSetters}; pub use hide_general_forum_topic::{HideGeneralForumTopic, HideGeneralForumTopicSetters}; @@ -216,6 +228,7 @@ pub use pin_chat_message::{PinChatMessage, PinChatMessageSetters}; pub use promote_chat_member::{PromoteChatMember, PromoteChatMemberSetters}; pub use reopen_forum_topic::{ReopenForumTopic, ReopenForumTopicSetters}; pub use reopen_general_forum_topic::{ReopenGeneralForumTopic, ReopenGeneralForumTopicSetters}; +pub use replace_sticker_in_set::{ReplaceStickerInSet, ReplaceStickerInSetSetters}; pub use restrict_chat_member::{RestrictChatMember, RestrictChatMemberSetters}; pub use revoke_chat_invite_link::{RevokeChatInviteLink, RevokeChatInviteLinkSetters}; pub use send_animation::{SendAnimation, SendAnimationSetters}; @@ -250,6 +263,7 @@ pub use set_custom_emoji_sticker_set_thumbnail::{ }; pub use set_game_score::{SetGameScore, SetGameScoreSetters}; pub use set_game_score_inline::{SetGameScoreInline, SetGameScoreInlineSetters}; +pub use set_message_reaction::{SetMessageReaction, SetMessageReactionSetters}; pub use set_my_commands::{SetMyCommands, SetMyCommandsSetters}; pub use set_my_default_administrator_rights::{ SetMyDefaultAdministratorRights, SetMyDefaultAdministratorRightsSetters, diff --git a/crates/teloxide-core/src/payloads/copy_message.rs b/crates/teloxide-core/src/payloads/copy_message.rs index 479cbae4..bc7a770a 100644 --- a/crates/teloxide-core/src/payloads/copy_message.rs +++ b/crates/teloxide-core/src/payloads/copy_message.rs @@ -2,7 +2,9 @@ use serde::Serialize; -use crate::types::{MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ThreadId}; +use crate::types::{ + MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ReplyParameters, ThreadId, +}; impl_payload! { /// Use this method to copy messages of any kind. The method is analogous to the method forwardMessage, but the copied message doesn't have a link to the original message. Returns the [`MessageId`] of the sent message on success. @@ -36,12 +38,9 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/copy_messages.rs b/crates/teloxide-core/src/payloads/copy_messages.rs new file mode 100644 index 00000000..90a4cd90 --- /dev/null +++ b/crates/teloxide-core/src/payloads/copy_messages.rs @@ -0,0 +1,35 @@ +//! Generated by `codegen_payloads`, do not edit by hand. + +use serde::Serialize; + +use crate::types::{MessageId, Recipient, ThreadId}; + +impl_payload! { + /// Use this method to copy messages of any kind. If some of the specified messages can't be found or copied, they are skipped. Service messages, giveaway messages, giveaway winners messages, and invoice messages can't be copied. A quiz poll can be copied only if the value of the field _correct\_option\_id_ is known to the bot. The method is analogous to the method [`ForwardMessages`], but the copied messages don't have a link to the original message. Album grouping is kept for copied messages. On success, an array of [`MessageId`] of the sent messages is returned. + /// + /// [`MessageId`]: crate::types::MessageId + /// [`ForwardMessages`]: crate::payloads::ForwardMessages + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub CopyMessages (CopyMessagesSetters) => Vec { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: Recipient [into], + /// Unique identifier for the chat where the original message was sent (or channel username in the format `@channelusername`) + pub from_chat_id: Recipient [into], + /// Identifiers of 1-100 messages in the chat _from\_chat\_id_ to copy. The identifiers must be specified in a strictly increasing order. + pub message_ids: Vec [collect], + } + optional { + /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only + pub message_thread_id: ThreadId, + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// Protects the contents of sent messages from forwarding and saving + pub protect_content: bool, + /// Pass _True_ to copy the messages without their captions + pub remove_caption: bool, + } + } +} diff --git a/crates/teloxide-core/src/payloads/create_forum_topic.rs b/crates/teloxide-core/src/payloads/create_forum_topic.rs index a1412fb3..ef9824f2 100644 --- a/crates/teloxide-core/src/payloads/create_forum_topic.rs +++ b/crates/teloxide-core/src/payloads/create_forum_topic.rs @@ -2,7 +2,7 @@ use serde::Serialize; -use crate::types::{ForumTopic, Recipient}; +use crate::types::{ForumTopic, Recipient, Rgb}; impl_payload! { /// Use this method to create a topic in a forum supergroup chat. The bot must be an administrator in the chat for this to work and must have the _can\_manage\_topics_ administrator rights. Returns information about the created topic as a `ForumTopic` object. @@ -13,8 +13,10 @@ impl_payload! { pub chat_id: Recipient [into], /// Topic name, 1-128 characters pub name: String [into], - /// Color of the topic icon in RGB format. Currently, must be one of 7322096 (0x6FB9F0), 16766590 (0xFFD67E), 13338331 (0xCB86DB), 9367192 (0x8EEE98), 16749490 (0xFF93B2), or 16478047 (0xFB6F5F) - pub icon_color: u32, + /// Color of the topic icon in RGB format. Currently, must be one of 7322096 (`0x6FB9F0`), 16766590 (`0xFFD67E`), 13338331 (`0xCB86DB`), 9367192 (`0x8EEE98`), 16749490 (`0xFF93B2`), or 16478047 (`0xFB6F5F`). To construct color from these values use [`Rgb::from_u32`] + /// + /// [`Rgb::from_u32`]: crate::types::Rgb::from_u32 + pub icon_color: Rgb, /// Unique identifier of the custom emoji shown as the topic icon. Use `getForumTopicIconStickers` to get all allowed custom emoji identifiers. pub icon_custom_emoji_id: String [into], } diff --git a/crates/teloxide-core/src/payloads/create_invoice_link.rs b/crates/teloxide-core/src/payloads/create_invoice_link.rs index 72748157..3640889c 100644 --- a/crates/teloxide-core/src/payloads/create_invoice_link.rs +++ b/crates/teloxide-core/src/payloads/create_invoice_link.rs @@ -19,7 +19,10 @@ impl_payload! { /// /// [Botfather]: https://t.me/botfather pub provider_token: String [into], - /// Three-letter ISO 4217 currency code, see more on currencies + /// Three-letter ISO 4217 currency code, see [more on currencies]. Pass `XTR` for payments in [Telegram Stars]. + /// + /// [more on currencies]: https://core.telegram.org/bots/payments#supported-currencies + /// [Telegram Stars]: https://t.me/BotNews/90 pub currency: String [into], /// Price breakdown, a JSON-serialized list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.) pub prices: Vec [collect], diff --git a/crates/teloxide-core/src/payloads/create_new_sticker_set.rs b/crates/teloxide-core/src/payloads/create_new_sticker_set.rs index 9ca7be3d..d3a41491 100644 --- a/crates/teloxide-core/src/payloads/create_new_sticker_set.rs +++ b/crates/teloxide-core/src/payloads/create_new_sticker_set.rs @@ -2,7 +2,7 @@ use serde::Serialize; -use crate::types::{InputSticker, StickerFormat, StickerType, True, UserId}; +use crate::types::{InputSticker, StickerType, True, UserId}; impl_payload! { /// Use this method to create a new sticker set owned by a user. The bot will be able to edit the sticker set thus created. Returns True on success. @@ -17,8 +17,6 @@ impl_payload! { pub title: String [into], /// A JSON-serialized list of 1-50 initial stickers to be added to the sticker set pub stickers: Vec [collect], - /// Format of the sticker, must be one of โ€œstaticโ€, โ€œanimatedโ€, โ€œvideoโ€ - pub sticker_format: StickerFormat, } optional { /// Type of stickers in the set, pass โ€œregularโ€, โ€œmaskโ€, or โ€œcustom_emojiโ€. By default, a regular sticker set is created. diff --git a/crates/teloxide-core/src/payloads/delete_messages.rs b/crates/teloxide-core/src/payloads/delete_messages.rs new file mode 100644 index 00000000..f8306c03 --- /dev/null +++ b/crates/teloxide-core/src/payloads/delete_messages.rs @@ -0,0 +1,20 @@ +//! Generated by `codegen_payloads`, do not edit by hand. + +use serde::Serialize; + +use crate::types::{MessageId, Recipient, True}; + +impl_payload! { + /// Use this method to delete multiple messages simultaneously. If some of the specified messages can't be found, they are skipped. Returns _True_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub DeleteMessages (DeleteMessagesSetters) => True { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`). + pub chat_id: Recipient [into], + /// Identifiers of 1-100 messages to delete. See [`DeleteMessage`] for limitations on which messages can be deleted + /// + /// [`DeleteMessage`]: crate::payloads::DeleteMessage + pub message_ids: Vec [collect], + } + } +} diff --git a/crates/teloxide-core/src/payloads/edit_message_live_location.rs b/crates/teloxide-core/src/payloads/edit_message_live_location.rs index b8df0e5a..a3168fe9 100644 --- a/crates/teloxide-core/src/payloads/edit_message_live_location.rs +++ b/crates/teloxide-core/src/payloads/edit_message_live_location.rs @@ -30,7 +30,7 @@ impl_payload! { pub heading: u16, /// For live locations, a maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified. pub proximity_alert_radius: u32, - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/edit_message_live_location_inline.rs b/crates/teloxide-core/src/payloads/edit_message_live_location_inline.rs index e0f6e5e4..936c8994 100644 --- a/crates/teloxide-core/src/payloads/edit_message_live_location_inline.rs +++ b/crates/teloxide-core/src/payloads/edit_message_live_location_inline.rs @@ -27,7 +27,7 @@ impl_payload! { pub heading: u16, /// For live locations, a maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified. pub proximity_alert_radius: u32, - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/edit_message_text.rs b/crates/teloxide-core/src/payloads/edit_message_text.rs index ba4329c5..833b3175 100644 --- a/crates/teloxide-core/src/payloads/edit_message_text.rs +++ b/crates/teloxide-core/src/payloads/edit_message_text.rs @@ -2,7 +2,10 @@ use serde::Serialize; -use crate::types::{InlineKeyboardMarkup, Message, MessageEntity, MessageId, ParseMode, Recipient}; +use crate::types::{ + InlineKeyboardMarkup, LinkPreviewOptions, Message, MessageEntity, MessageId, ParseMode, + Recipient, +}; impl_payload! { /// Use this method to edit text and [games] messages. On success, the edited Message is returned. @@ -28,8 +31,8 @@ impl_payload! { pub parse_mode: ParseMode, /// List of special entities that appear in message text, which can be specified instead of _parse\_mode_ pub entities: Vec [collect], - /// Disables link previews for links in this message - pub disable_web_page_preview: bool, + /// Link preview generation options for the message + pub link_preview_options: LinkPreviewOptions, /// A JSON-serialized object for an [inline keyboard]. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/forward_messages.rs b/crates/teloxide-core/src/payloads/forward_messages.rs new file mode 100644 index 00000000..e191725b --- /dev/null +++ b/crates/teloxide-core/src/payloads/forward_messages.rs @@ -0,0 +1,32 @@ +//! Generated by `codegen_payloads`, do not edit by hand. + +use serde::Serialize; + +use crate::types::{MessageId, Recipient, ThreadId}; + +impl_payload! { + /// Use this method to forward multiple messages of any kind. If some of the specified messages can't be found or forwarded, they are skipped. Service messages and messages with protected content can't be forwarded. Album grouping is kept for forwarded messages. On success, an array of [`MessageId`] of the sent messages is returned. + /// + /// [`MessageId`]: crate::types::MessageId + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub ForwardMessages (ForwardMessagesSetters) => Vec { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: Recipient [into], + /// Unique identifier for the chat where the original message was sent (or channel username in the format `@channelusername`) + pub from_chat_id: Recipient [into], + /// A JSON-serialized list of 1-100 identifiers of messages in the chat _from\_chat\_id_ to forward. The identifiers must be specified in a strictly increasing order. + pub message_ids: Vec [collect], + } + optional { + /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only + pub message_thread_id: ThreadId, + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// Protects the contents of sent messages from forwarding and saving + pub protect_content: bool, + } + } +} diff --git a/crates/teloxide-core/src/payloads/get_business_connection.rs b/crates/teloxide-core/src/payloads/get_business_connection.rs new file mode 100644 index 00000000..1ae65748 --- /dev/null +++ b/crates/teloxide-core/src/payloads/get_business_connection.rs @@ -0,0 +1,16 @@ +//! Generated by `codegen_payloads`, do not edit by hand. + +use serde::Serialize; + +use crate::types::{BusinessConnection, BusinessConnectionId}; + +impl_payload! { + /// Use this method to get information about the connection of the bot with a business account. Returns a BusinessConnection object on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub GetBusinessConnection (GetBusinessConnectionSetters) => BusinessConnection { + required { + /// Unique identifier of the business connection + pub business_connection_id: BusinessConnectionId, + } + } +} diff --git a/crates/teloxide-core/src/payloads/get_user_chat_boosts.rs b/crates/teloxide-core/src/payloads/get_user_chat_boosts.rs new file mode 100644 index 00000000..6cbfadc3 --- /dev/null +++ b/crates/teloxide-core/src/payloads/get_user_chat_boosts.rs @@ -0,0 +1,20 @@ +//! Generated by `codegen_payloads`, do not edit by hand. + +use serde::Serialize; + +use crate::types::{Recipient, UserChatBoosts, UserId}; + +impl_payload! { + /// Use this method to get the list of boosts added to a chat by a user. Requires administrator rights in the chat. Returns a [`UserChatBoosts`] object. + /// + /// [`UserChatBoosts`]: crate::types::UserChatBoosts + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub GetUserChatBoosts (GetUserChatBoostsSetters) => UserChatBoosts { + required { + /// Unique identifier for the chat or username of the channel (in the format @channelusername) + pub chat_id: Recipient [into], + /// Unique identifier of the target user + pub user_id: UserId, + } + } +} diff --git a/crates/teloxide-core/src/payloads/promote_chat_member.rs b/crates/teloxide-core/src/payloads/promote_chat_member.rs index 851abdd4..7512ee97 100644 --- a/crates/teloxide-core/src/payloads/promote_chat_member.rs +++ b/crates/teloxide-core/src/payloads/promote_chat_member.rs @@ -25,11 +25,11 @@ impl_payload! { pub can_edit_messages: bool, /// Pass True, if the administrator can delete messages of other users pub can_delete_messages: bool, - /// Pass True, if the administrator can post stories in the channel, channels only + /// Pass True, if the administrator can post stories to the chat pub can_post_stories: bool, - /// Pass True, if the administrator can edit stories posted by other users, channels only + /// Pass True, if the administrator can edit stories posted by other users pub can_edit_stories: bool, - /// Pass True, if the administrator can delete stories posted by other users, channels only + /// Pass True, if the administrator can delete stories posted by other users pub can_delete_stories: bool, /// Pass True, if the administrator can manage video chats, supergroups only pub can_manage_video_chats: bool, diff --git a/crates/teloxide-core/src/payloads/replace_sticker_in_set.rs b/crates/teloxide-core/src/payloads/replace_sticker_in_set.rs new file mode 100644 index 00000000..8e64e9b5 --- /dev/null +++ b/crates/teloxide-core/src/payloads/replace_sticker_in_set.rs @@ -0,0 +1,27 @@ +//! Generated by `codegen_payloads`, do not edit by hand. + +use serde::Serialize; + +use crate::types::{InputSticker, True, UserId}; + +impl_payload! { + @[multipart = sticker] + /// Use this method to replace an existing sticker in a sticker set with a new one. The method is equivalent to calling [DeleteStickerFromSet], then [AddStickerToSet], then [SetStickerPositionInSet]. Returns _True_ on success. + /// + /// [DeleteStickerFromSet]: https://docs.rs/teloxide/latest/teloxide/payloads/struct.DeleteStickerFromSet.html + /// [AddStickerToSet]: https://docs.rs/teloxide/latest/teloxide/payloads/struct.AddStickerToSet.html + /// [SetStickerPositionInSet]: https://docs.rs/teloxide/latest/teloxide/payloads/struct.SetStickerPositionInSet.html + #[derive(Debug, Clone, Serialize)] + pub ReplaceStickerInSet (ReplaceStickerInSetSetters) => True { + required { + /// User identifier of the sticker set owner + pub user_id: UserId, + /// Sticker set name + pub name: String [into], + /// File identifier of the replaced sticker + pub old_sticker: String [into], + /// A JSON-serialized object with information about the added sticker. If exactly the same sticker had already been added to the set, then the set remains unchanged. + pub sticker: InputSticker, + } + } +} diff --git a/crates/teloxide-core/src/payloads/send_animation.rs b/crates/teloxide-core/src/payloads/send_animation.rs index 500a66a5..d1fd7758 100644 --- a/crates/teloxide-core/src/payloads/send_animation.rs +++ b/crates/teloxide-core/src/payloads/send_animation.rs @@ -3,7 +3,8 @@ use serde::Serialize; use crate::types::{ - InputFile, Message, MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ThreadId, + BusinessConnectionId, InputFile, Message, MessageEntity, ParseMode, Recipient, ReplyMarkup, + ReplyParameters, ThreadId, }; impl_payload! { @@ -22,6 +23,8 @@ impl_payload! { pub animation: InputFile, } optional { + /// Unique identifier of the business connection on behalf of which the message will be sent + pub business_connection_id: BusinessConnectionId, /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only pub message_thread_id: ThreadId, /// Duration of the animation in seconds @@ -50,12 +53,9 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/send_audio.rs b/crates/teloxide-core/src/payloads/send_audio.rs index fcb12f4d..1b0829eb 100644 --- a/crates/teloxide-core/src/payloads/send_audio.rs +++ b/crates/teloxide-core/src/payloads/send_audio.rs @@ -3,7 +3,8 @@ use serde::Serialize; use crate::types::{ - InputFile, Message, MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ThreadId, + BusinessConnectionId, InputFile, Message, MessageEntity, ParseMode, Recipient, ReplyMarkup, + ReplyParameters, ThreadId, }; impl_payload! { @@ -25,6 +26,8 @@ impl_payload! { pub audio: InputFile, } optional { + /// Unique identifier of the business connection on behalf of which the message will be sent + pub business_connection_id: BusinessConnectionId, /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only pub message_thread_id: ThreadId, /// Audio caption, 0-1024 characters after entities parsing @@ -51,12 +54,9 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/send_chat_action.rs b/crates/teloxide-core/src/payloads/send_chat_action.rs index 46726fba..898a84e3 100644 --- a/crates/teloxide-core/src/payloads/send_chat_action.rs +++ b/crates/teloxide-core/src/payloads/send_chat_action.rs @@ -2,7 +2,7 @@ use serde::Serialize; -use crate::types::{ChatAction, Recipient, ThreadId, True}; +use crate::types::{BusinessConnectionId, ChatAction, Recipient, ThreadId, True}; impl_payload! { /// Use this method when you need to tell the user that something is happening on the bot's side. The status is set for 5 seconds or less (when a message arrives from your bot, Telegram clients clear its typing status). Returns True on success. @@ -30,6 +30,8 @@ impl_payload! { pub action: ChatAction, } optional { + /// Unique identifier of the business connection on behalf of which the action will be sent + pub business_connection_id: BusinessConnectionId, /// Unique identifier for the target message thread; supergroups only pub message_thread_id: ThreadId, } diff --git a/crates/teloxide-core/src/payloads/send_contact.rs b/crates/teloxide-core/src/payloads/send_contact.rs index 7179cbcc..7f395d5d 100644 --- a/crates/teloxide-core/src/payloads/send_contact.rs +++ b/crates/teloxide-core/src/payloads/send_contact.rs @@ -2,7 +2,9 @@ use serde::Serialize; -use crate::types::{Message, MessageId, Recipient, ReplyMarkup, ThreadId}; +use crate::types::{ + BusinessConnectionId, Message, Recipient, ReplyMarkup, ReplyParameters, ThreadId, +}; impl_payload! { /// Use this method to send phone contacts. On success, the sent [`Message`] is returned. @@ -19,6 +21,8 @@ impl_payload! { pub first_name: String [into], } optional { + /// Unique identifier of the business connection on behalf of which the message will be sent + pub business_connection_id: BusinessConnectionId, /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only pub message_thread_id: ThreadId, /// Contact's last name @@ -33,12 +37,9 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/send_dice.rs b/crates/teloxide-core/src/payloads/send_dice.rs index 2a00cbd8..069a9507 100644 --- a/crates/teloxide-core/src/payloads/send_dice.rs +++ b/crates/teloxide-core/src/payloads/send_dice.rs @@ -2,7 +2,9 @@ use serde::Serialize; -use crate::types::{DiceEmoji, Message, MessageId, Recipient, ReplyMarkup, ThreadId}; +use crate::types::{ + BusinessConnectionId, DiceEmoji, Message, Recipient, ReplyMarkup, ReplyParameters, ThreadId, +}; impl_payload! { /// Use this method to send an animated emoji that will display a random value. On success, the sent [`Message`] is returned. @@ -15,6 +17,8 @@ impl_payload! { pub chat_id: Recipient [into], } optional { + /// Unique identifier of the business connection on behalf of which the message will be sent + pub business_connection_id: BusinessConnectionId, /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only pub message_thread_id: ThreadId, /// Emoji on which the dice throw animation is based. Currently, must be one of โ€œ๐ŸŽฒโ€, โ€œ๐ŸŽฏโ€, โ€œ๐Ÿ€โ€, โ€œโšฝโ€, โ€œ๐ŸŽณโ€, or โ€œ๐ŸŽฐโ€. Dice can have values 1-6 for โ€œ๐ŸŽฒโ€, โ€œ๐ŸŽฏโ€ and โ€œ๐ŸŽณโ€, values 1-5 for โ€œ๐Ÿ€โ€ and โ€œโšฝโ€, and values 1-64 for โ€œ๐ŸŽฐโ€. Defaults to โ€œ๐ŸŽฒโ€ @@ -25,12 +29,9 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/send_document.rs b/crates/teloxide-core/src/payloads/send_document.rs index cfe41cc4..43e6f7d1 100644 --- a/crates/teloxide-core/src/payloads/send_document.rs +++ b/crates/teloxide-core/src/payloads/send_document.rs @@ -3,7 +3,8 @@ use serde::Serialize; use crate::types::{ - InputFile, Message, MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ThreadId, + BusinessConnectionId, InputFile, Message, MessageEntity, ParseMode, Recipient, ReplyMarkup, + ReplyParameters, ThreadId, }; impl_payload! { @@ -22,6 +23,8 @@ impl_payload! { pub document: InputFile, } optional { + /// Unique identifier of the business connection on behalf of which the message will be sent + pub business_connection_id: BusinessConnectionId, /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only pub message_thread_id: ThreadId, /// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass โ€œattach://โ€ if the thumbnail was uploaded using multipart/form-data under . [More info on Sending Files ยป] @@ -44,12 +47,9 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/send_game.rs b/crates/teloxide-core/src/payloads/send_game.rs index bb2b02c2..413e57ba 100644 --- a/crates/teloxide-core/src/payloads/send_game.rs +++ b/crates/teloxide-core/src/payloads/send_game.rs @@ -2,7 +2,7 @@ use serde::Serialize; -use crate::types::{ChatId, Message, MessageId, ReplyMarkup, ThreadId}; +use crate::types::{BusinessConnectionId, ChatId, Message, ReplyMarkup, ReplyParameters, ThreadId}; impl_payload! { /// Use this method to send a game. On success, the sent [`Message`] is returned. @@ -17,6 +17,8 @@ impl_payload! { pub game_short_name: String [into], } optional { + /// Unique identifier of the business connection on behalf of which the message will be sent + pub business_connection_id: BusinessConnectionId, /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only pub message_thread_id: ThreadId, /// Sends the message [silently]. Users will receive a notification with no sound. @@ -25,12 +27,9 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, - /// A JSON-serialized object for an [inline keyboard]. If empty, one 'Play game_title' button will be shown. If not empty, the first button must launch the game. + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, + /// A JSON-serialized object for an [inline keyboard]. If empty, one 'Play game_title' button will be shown. If not empty, the first button must launch the game. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating pub reply_markup: ReplyMarkup [into], diff --git a/crates/teloxide-core/src/payloads/send_invoice.rs b/crates/teloxide-core/src/payloads/send_invoice.rs index 278c7dcf..342863a2 100644 --- a/crates/teloxide-core/src/payloads/send_invoice.rs +++ b/crates/teloxide-core/src/payloads/send_invoice.rs @@ -3,7 +3,9 @@ use serde::Serialize; use url::Url; -use crate::types::{InlineKeyboardMarkup, LabeledPrice, Message, MessageId, Recipient, ThreadId}; +use crate::types::{ + InlineKeyboardMarkup, LabeledPrice, Message, Recipient, ReplyParameters, ThreadId, +}; impl_payload! { /// Use this method to send invoices. On success, the sent [`Message`] is returned. @@ -24,7 +26,10 @@ impl_payload! { /// /// [Botfather]: https://t.me/botfather pub provider_token: String [into], - /// Three-letter ISO 4217 currency code, see more on currencies + /// Three-letter ISO 4217 currency code, see [more on currencies]. Pass `XTR` for payments in [Telegram Stars]. + /// + /// [more on currencies]: https://core.telegram.org/bots/payments#supported-currencies + /// [Telegram Stars]: https://t.me/BotNews/90 pub currency: String [into], /// Price breakdown, a JSON-serialized list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.) pub prices: Vec [collect], @@ -70,11 +75,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// A JSON-serialized object for an [inline keyboard]. If empty, one 'Pay `total price`' button will be shown. If not empty, the first button must be a Pay button. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_location.rs b/crates/teloxide-core/src/payloads/send_location.rs index 5d61cbe1..03094933 100644 --- a/crates/teloxide-core/src/payloads/send_location.rs +++ b/crates/teloxide-core/src/payloads/send_location.rs @@ -2,7 +2,9 @@ use serde::Serialize; -use crate::types::{Message, MessageId, Recipient, ReplyMarkup, ThreadId}; +use crate::types::{ + BusinessConnectionId, Message, Recipient, ReplyMarkup, ReplyParameters, ThreadId, +}; impl_payload! { /// Use this method to send point on the map. On success, the sent [`Message`] is returned. @@ -19,6 +21,8 @@ impl_payload! { pub longitude: f64, } optional { + /// Unique identifier of the business connection on behalf of which the message will be sent + pub business_connection_id: BusinessConnectionId, /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only pub message_thread_id: ThreadId, /// The radius of uncertainty for the location, measured in meters; 0-1500 @@ -37,12 +41,9 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/send_media_group.rs b/crates/teloxide-core/src/payloads/send_media_group.rs index f5300c84..ab5782a1 100644 --- a/crates/teloxide-core/src/payloads/send_media_group.rs +++ b/crates/teloxide-core/src/payloads/send_media_group.rs @@ -2,7 +2,9 @@ use serde::Serialize; -use crate::types::{InputMedia, Message, MessageId, Recipient, ThreadId}; +use crate::types::{ + BusinessConnectionId, InputMedia, Message, Recipient, ReplyParameters, ThreadId, +}; impl_payload! { /// Use this method to send a group of photos, videos, documents or audios as an album. Documents and audio files can be only grouped in an album with messages of the same type. On success, an array of [`Message`]s that were sent is returned. @@ -26,6 +28,8 @@ impl_payload! { pub media: Vec [collect], } optional { + /// Unique identifier of the business connection on behalf of which the message will be sent + pub business_connection_id: BusinessConnectionId, /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only pub message_thread_id: ThreadId, /// Sends the message [silently]. Users will receive a notification with no sound. @@ -34,11 +38,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, } } } diff --git a/crates/teloxide-core/src/payloads/send_message.rs b/crates/teloxide-core/src/payloads/send_message.rs index b243aec3..46d1c616 100644 --- a/crates/teloxide-core/src/payloads/send_message.rs +++ b/crates/teloxide-core/src/payloads/send_message.rs @@ -3,7 +3,8 @@ use serde::Serialize; use crate::types::{ - Message, MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ThreadId, + BusinessConnectionId, LinkPreviewOptions, Message, MessageEntity, ParseMode, Recipient, + ReplyMarkup, ReplyParameters, ThreadId, }; impl_payload! { @@ -19,6 +20,8 @@ impl_payload! { pub text: String [into], } optional { + /// Unique identifier of the business connection on behalf of which the message will be sent + pub business_connection_id: BusinessConnectionId, /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only pub message_thread_id: ThreadId, /// Mode for parsing entities in the message text. See [formatting options] for more details. @@ -27,20 +30,17 @@ impl_payload! { pub parse_mode: ParseMode, /// List of special entities that appear in the message text, which can be specified instead of _parse\_mode_ pub entities: Vec [collect], - /// Disables link previews for links in this message - pub disable_web_page_preview: bool, + /// Link preview generation options for the message + pub link_preview_options: LinkPreviewOptions, /// Sends the message [silently]. Users will receive a notification with no sound. /// /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/send_photo.rs b/crates/teloxide-core/src/payloads/send_photo.rs index 0c89cc90..037c3935 100644 --- a/crates/teloxide-core/src/payloads/send_photo.rs +++ b/crates/teloxide-core/src/payloads/send_photo.rs @@ -3,7 +3,8 @@ use serde::Serialize; use crate::types::{ - InputFile, Message, MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ThreadId, + BusinessConnectionId, InputFile, Message, MessageEntity, ParseMode, Recipient, ReplyMarkup, + ReplyParameters, ThreadId, }; impl_payload! { @@ -22,6 +23,8 @@ impl_payload! { pub photo: InputFile, } optional { + /// Unique identifier of the business connection on behalf of which the message will be sent + pub business_connection_id: BusinessConnectionId, /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only pub message_thread_id: ThreadId, /// Photo caption (may also be used when resending photos by _file\_id_), 0-1024 characters after entities parsing @@ -40,12 +43,9 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/send_poll.rs b/crates/teloxide-core/src/payloads/send_poll.rs index 080b63d2..4704cc60 100644 --- a/crates/teloxide-core/src/payloads/send_poll.rs +++ b/crates/teloxide-core/src/payloads/send_poll.rs @@ -4,11 +4,12 @@ use chrono::{DateTime, Utc}; use serde::Serialize; use crate::types::{ - Message, MessageEntity, MessageId, ParseMode, PollType, Recipient, ReplyMarkup, ThreadId, + BusinessConnectionId, Message, MessageEntity, ParseMode, PollType, Recipient, ReplyMarkup, + ReplyParameters, ThreadId, }; impl_payload! { - /// Use this method to send phone contacts. On success, the sent [`Message`] is returned. + /// Use this method to send a native poll. On success, the sent [`Message`] is returned. /// /// [`Message`]: crate::types::Message #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] @@ -22,6 +23,8 @@ impl_payload! { pub options: Vec [collect], } optional { + /// Unique identifier of the business connection on behalf of which the message will be sent + pub business_connection_id: BusinessConnectionId, /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only pub message_thread_id: ThreadId, /// True, if the poll needs to be anonymous, defaults to True @@ -54,12 +57,9 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/send_sticker.rs b/crates/teloxide-core/src/payloads/send_sticker.rs index 72dd9e2f..754615e4 100644 --- a/crates/teloxide-core/src/payloads/send_sticker.rs +++ b/crates/teloxide-core/src/payloads/send_sticker.rs @@ -2,11 +2,13 @@ use serde::Serialize; -use crate::types::{InputFile, Message, MessageId, Recipient, ReplyMarkup, ThreadId}; +use crate::types::{ + BusinessConnectionId, InputFile, Message, Recipient, ReplyMarkup, ReplyParameters, ThreadId, +}; impl_payload! { @[multipart = sticker] - /// Use this method to send static .WEBP or [animated] .TGS stickers. On success, the sent Message is returned. + /// Use this method to send static .WEBP, .TGS or .WEBM stickers. On success, the sent Message is returned. /// /// [animated]: https://telegram.org/blog/animated-stickers #[derive(Debug, Clone, Serialize)] @@ -20,6 +22,8 @@ impl_payload! { pub sticker: InputFile, } optional { + /// Unique identifier of the business connection on behalf of which the message will be sent + pub business_connection_id: BusinessConnectionId, /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only pub message_thread_id: ThreadId, /// Emoji associated with the sticker; only for just uploaded stickers @@ -30,12 +34,9 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/send_venue.rs b/crates/teloxide-core/src/payloads/send_venue.rs index 59fcdcbb..e9629e39 100644 --- a/crates/teloxide-core/src/payloads/send_venue.rs +++ b/crates/teloxide-core/src/payloads/send_venue.rs @@ -2,7 +2,9 @@ use serde::Serialize; -use crate::types::{Message, MessageId, Recipient, ReplyMarkup, ThreadId}; +use crate::types::{ + BusinessConnectionId, Message, Recipient, ReplyMarkup, ReplyParameters, ThreadId, +}; impl_payload! { /// Use this method to send information about a venue. On success, the sent [`Message`] is returned. @@ -23,6 +25,8 @@ impl_payload! { pub address: String [into], } optional { + /// Unique identifier of the business connection on behalf of which the message will be sent + pub business_connection_id: BusinessConnectionId, /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only pub message_thread_id: ThreadId, /// Foursquare identifier of the venue @@ -41,12 +45,9 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/send_video.rs b/crates/teloxide-core/src/payloads/send_video.rs index 4ecfc6bf..c4de05f1 100644 --- a/crates/teloxide-core/src/payloads/send_video.rs +++ b/crates/teloxide-core/src/payloads/send_video.rs @@ -3,7 +3,8 @@ use serde::Serialize; use crate::types::{ - InputFile, Message, MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ThreadId, + BusinessConnectionId, InputFile, Message, MessageEntity, ParseMode, Recipient, ReplyMarkup, + ReplyParameters, ThreadId, }; impl_payload! { @@ -23,6 +24,8 @@ impl_payload! { pub video: InputFile, } optional { + /// Unique identifier of the business connection on behalf of which the message will be sent + pub business_connection_id: BusinessConnectionId, /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only pub message_thread_id: ThreadId, /// Duration of the video in seconds @@ -53,12 +56,9 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/send_video_note.rs b/crates/teloxide-core/src/payloads/send_video_note.rs index 5b7afd9a..72c1e60b 100644 --- a/crates/teloxide-core/src/payloads/send_video_note.rs +++ b/crates/teloxide-core/src/payloads/send_video_note.rs @@ -2,7 +2,9 @@ use serde::Serialize; -use crate::types::{InputFile, Message, MessageId, Recipient, ReplyMarkup, ThreadId}; +use crate::types::{ + BusinessConnectionId, InputFile, Message, Recipient, ReplyMarkup, ReplyParameters, ThreadId, +}; impl_payload! { @[multipart = video_note, thumbnail] @@ -21,6 +23,8 @@ impl_payload! { pub video_note: InputFile, } optional { + /// Unique identifier of the business connection on behalf of which the message will be sent + pub business_connection_id: BusinessConnectionId, /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only pub message_thread_id: ThreadId, /// Duration of the video in seconds @@ -37,12 +41,9 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/send_voice.rs b/crates/teloxide-core/src/payloads/send_voice.rs index 5e5293ed..40b85c42 100644 --- a/crates/teloxide-core/src/payloads/send_voice.rs +++ b/crates/teloxide-core/src/payloads/send_voice.rs @@ -3,7 +3,8 @@ use serde::Serialize; use crate::types::{ - InputFile, Message, MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ThreadId, + BusinessConnectionId, InputFile, Message, MessageEntity, ParseMode, Recipient, ReplyMarkup, + ReplyParameters, ThreadId, }; impl_payload! { @@ -24,6 +25,8 @@ impl_payload! { pub voice: InputFile, } optional { + /// Unique identifier of the business connection on behalf of which the message will be sent + pub business_connection_id: BusinessConnectionId, /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only pub message_thread_id: ThreadId, /// Voice message caption, 0-1024 characters after entities parsing @@ -40,12 +43,9 @@ impl_payload! { /// /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/set_message_reaction.rs b/crates/teloxide-core/src/payloads/set_message_reaction.rs new file mode 100644 index 00000000..834336ba --- /dev/null +++ b/crates/teloxide-core/src/payloads/set_message_reaction.rs @@ -0,0 +1,25 @@ +//! Generated by `codegen_payloads`, do not edit by hand. + +use serde::Serialize; + +use crate::types::{MessageId, ReactionType, Recipient, True}; + +impl_payload! { + /// Use this method to change the chosen reactions on a message. Service messages can't be reacted to. Automatically forwarded messages from a channel to its discussion group have the same available reactions as messages in the channel. Returns True on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SetMessageReaction (SetMessageReactionSetters) => True { + required { + /// Unique identifier for the target chat or username of the target channel (in the format @channelusername) + pub chat_id: Recipient [into], + /// Identifier of the target message. If the message belongs to a media group, the reaction is set to the first non-deleted message in the group instead. + #[serde(flatten)] + pub message_id: MessageId, + } + optional { + /// New list of reaction types to set on the message. Currently, as non-premium users, bots can set up to one reaction per message. A custom emoji reaction can be used if it is either already present on the message or explicitly allowed by chat administrators. + pub reaction: Vec [collect], + /// Pass True to set the reaction with a big animation + pub is_big: bool, + } + } +} diff --git a/crates/teloxide-core/src/payloads/set_sticker_set_thumbnail.rs b/crates/teloxide-core/src/payloads/set_sticker_set_thumbnail.rs index 4c0d6835..0c1d0690 100644 --- a/crates/teloxide-core/src/payloads/set_sticker_set_thumbnail.rs +++ b/crates/teloxide-core/src/payloads/set_sticker_set_thumbnail.rs @@ -2,7 +2,7 @@ use serde::Serialize; -use crate::types::{InputFile, True, UserId}; +use crate::types::{InputFile, StickerFormat, True, UserId}; impl_payload! { @[multipart = thumbnail] @@ -14,6 +14,8 @@ impl_payload! { pub name: String [into], /// User identifier of sticker file owner pub user_id: UserId, + /// Format of the thumbnail, must be one of "static" for a .WEBP or .PNG image, "animated" for a .TGS animation, or "video" for a WEBM video + pub format: StickerFormat, } optional { /// A .WEBP or .PNG image with the thumbnail, must be up to 128 kilobytes in size and have a width and height of exactly 100px, or a .TGS animation with a thumbnail up to 32 kilobytes in size (see https://core.telegram.org/stickers#animated-sticker-requirements for animated sticker technical requirements), or a WEBM video with the thumbnail up to 32 kilobytes in size; see https://core.telegram.org/stickers#video-sticker-requirements for video sticker technical requirements. Pass a file_id as a String to send a file that already exists on the Telegram servers, 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. Animated and video sticker set thumbnails can't be uploaded via HTTP URL. If omitted, then the thumbnail is dropped and the first sticker is used as the thumbnail. diff --git a/crates/teloxide-core/src/payloads/setters.rs b/crates/teloxide-core/src/payloads/setters.rs index 9771e38b..e7d3640c 100644 --- a/crates/teloxide-core/src/payloads/setters.rs +++ b/crates/teloxide-core/src/payloads/setters.rs @@ -6,31 +6,34 @@ pub use crate::payloads::{ AnswerPreCheckoutQuerySetters as _, AnswerShippingQuerySetters as _, AnswerWebAppQuerySetters as _, ApproveChatJoinRequestSetters as _, BanChatMemberSetters as _, BanChatSenderChatSetters as _, CloseForumTopicSetters as _, CloseGeneralForumTopicSetters as _, - CloseSetters as _, CopyMessageSetters as _, CreateChatInviteLinkSetters as _, - CreateForumTopicSetters as _, CreateInvoiceLinkSetters as _, CreateNewStickerSetSetters as _, - DeclineChatJoinRequestSetters as _, DeleteChatPhotoSetters as _, - DeleteChatStickerSetSetters as _, DeleteForumTopicSetters as _, DeleteMessageSetters as _, - DeleteMyCommandsSetters as _, DeleteStickerFromSetSetters as _, DeleteStickerSetSetters as _, - DeleteWebhookSetters as _, EditChatInviteLinkSetters as _, EditForumTopicSetters as _, - EditGeneralForumTopicSetters as _, EditMessageCaptionInlineSetters as _, - EditMessageCaptionSetters as _, EditMessageLiveLocationInlineSetters as _, - EditMessageLiveLocationSetters as _, EditMessageMediaInlineSetters as _, - EditMessageMediaSetters as _, EditMessageReplyMarkupInlineSetters as _, - EditMessageReplyMarkupSetters as _, EditMessageTextInlineSetters as _, - EditMessageTextSetters as _, ExportChatInviteLinkSetters as _, ForwardMessageSetters as _, - GetChatAdministratorsSetters as _, GetChatMemberCountSetters as _, GetChatMemberSetters as _, - GetChatMembersCountSetters as _, GetChatMenuButtonSetters as _, GetChatSetters as _, - GetCustomEmojiStickersSetters as _, GetFileSetters as _, GetForumTopicIconStickersSetters as _, - GetGameHighScoresSetters as _, GetMeSetters as _, GetMyCommandsSetters as _, - GetMyDefaultAdministratorRightsSetters as _, GetMyDescriptionSetters as _, - GetMyNameSetters as _, GetMyShortDescriptionSetters as _, GetStickerSetSetters as _, - GetUpdatesSetters as _, GetUserProfilePhotosSetters as _, GetWebhookInfoSetters as _, + CloseSetters as _, CopyMessageSetters as _, CopyMessagesSetters as _, + CreateChatInviteLinkSetters as _, CreateForumTopicSetters as _, CreateInvoiceLinkSetters as _, + CreateNewStickerSetSetters as _, DeclineChatJoinRequestSetters as _, + DeleteChatPhotoSetters as _, DeleteChatStickerSetSetters as _, DeleteForumTopicSetters as _, + DeleteMessageSetters as _, DeleteMessagesSetters as _, DeleteMyCommandsSetters as _, + DeleteStickerFromSetSetters as _, DeleteStickerSetSetters as _, DeleteWebhookSetters as _, + EditChatInviteLinkSetters as _, EditForumTopicSetters as _, EditGeneralForumTopicSetters as _, + EditMessageCaptionInlineSetters as _, EditMessageCaptionSetters as _, + EditMessageLiveLocationInlineSetters as _, EditMessageLiveLocationSetters as _, + EditMessageMediaInlineSetters as _, EditMessageMediaSetters as _, + EditMessageReplyMarkupInlineSetters as _, EditMessageReplyMarkupSetters as _, + EditMessageTextInlineSetters as _, EditMessageTextSetters as _, + ExportChatInviteLinkSetters as _, ForwardMessageSetters as _, ForwardMessagesSetters as _, + GetBusinessConnectionSetters as _, GetChatAdministratorsSetters as _, + GetChatMemberCountSetters as _, GetChatMemberSetters as _, GetChatMembersCountSetters as _, + GetChatMenuButtonSetters as _, GetChatSetters as _, GetCustomEmojiStickersSetters as _, + GetFileSetters as _, GetForumTopicIconStickersSetters as _, GetGameHighScoresSetters as _, + GetMeSetters as _, GetMyCommandsSetters as _, GetMyDefaultAdministratorRightsSetters as _, + GetMyDescriptionSetters as _, GetMyNameSetters as _, GetMyShortDescriptionSetters as _, + GetStickerSetSetters as _, GetUpdatesSetters as _, GetUserChatBoostsSetters as _, + GetUserProfilePhotosSetters as _, GetWebhookInfoSetters as _, HideGeneralForumTopicSetters as _, KickChatMemberSetters as _, LeaveChatSetters as _, LogOutSetters as _, PinChatMessageSetters as _, PromoteChatMemberSetters as _, ReopenForumTopicSetters as _, ReopenGeneralForumTopicSetters as _, - RestrictChatMemberSetters as _, RevokeChatInviteLinkSetters as _, SendAnimationSetters as _, - SendAudioSetters as _, SendChatActionSetters as _, SendContactSetters as _, - SendDiceSetters as _, SendDocumentSetters as _, SendGameSetters as _, SendInvoiceSetters as _, + ReplaceStickerInSetSetters as _, RestrictChatMemberSetters as _, + RevokeChatInviteLinkSetters as _, SendAnimationSetters as _, SendAudioSetters as _, + SendChatActionSetters as _, SendContactSetters as _, SendDiceSetters as _, + SendDocumentSetters as _, SendGameSetters as _, SendInvoiceSetters as _, SendLocationSetters as _, SendMediaGroupSetters as _, SendMessageSetters as _, SendPhotoSetters as _, SendPollSetters as _, SendStickerSetters as _, SendVenueSetters as _, SendVideoNoteSetters as _, SendVideoSetters as _, SendVoiceSetters as _, @@ -38,7 +41,7 @@ pub use crate::payloads::{ SetChatMenuButtonSetters as _, SetChatPermissionsSetters as _, SetChatPhotoSetters as _, SetChatStickerSetSetters as _, SetChatTitleSetters as _, SetCustomEmojiStickerSetThumbnailSetters as _, SetGameScoreInlineSetters as _, - SetGameScoreSetters as _, SetMyCommandsSetters as _, + SetGameScoreSetters as _, SetMessageReactionSetters as _, SetMyCommandsSetters as _, SetMyDefaultAdministratorRightsSetters as _, SetMyDescriptionSetters as _, SetMyNameSetters as _, SetMyShortDescriptionSetters as _, SetPassportDataErrorsSetters as _, SetStickerEmojiListSetters as _, SetStickerKeywordsSetters as _, diff --git a/crates/teloxide-core/src/payloads/stop_message_live_location.rs b/crates/teloxide-core/src/payloads/stop_message_live_location.rs index 7e2a312c..28d5c9b7 100644 --- a/crates/teloxide-core/src/payloads/stop_message_live_location.rs +++ b/crates/teloxide-core/src/payloads/stop_message_live_location.rs @@ -21,7 +21,7 @@ impl_payload! { pub message_id: MessageId, } optional { - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/stop_message_live_location_inline.rs b/crates/teloxide-core/src/payloads/stop_message_live_location_inline.rs index fdc7543c..c932f5f6 100644 --- a/crates/teloxide-core/src/payloads/stop_message_live_location_inline.rs +++ b/crates/teloxide-core/src/payloads/stop_message_live_location_inline.rs @@ -17,7 +17,7 @@ impl_payload! { pub inline_message_id: String [into], } optional { - /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. + /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove a reply keyboard or to force a reply from the user. Not supported for messages sent on behalf of a business account. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards diff --git a/crates/teloxide-core/src/payloads/upload_sticker_file.rs b/crates/teloxide-core/src/payloads/upload_sticker_file.rs index ef00a015..d3adc35f 100644 --- a/crates/teloxide-core/src/payloads/upload_sticker_file.rs +++ b/crates/teloxide-core/src/payloads/upload_sticker_file.rs @@ -6,10 +6,11 @@ use crate::types::{FileMeta, InputFile, StickerFormat, UserId}; impl_payload! { @[multipart = sticker] - /// Use this method to upload a file with a sticker for later use in the [CreateNewStickerSet] and [AddStickerToSet] methods (the file can be used multiple times). Returns the uploaded [`File`] on success. + /// Use this method to upload a file with a sticker for later use in the [CreateNewStickerSet], [AddStickerToSet] or [ReplaceStickerInSet] methods (the file can be used multiple times). Returns the uploaded [`File`] on success. /// /// [CreateNewStickerSet]: https://docs.rs/teloxide/latest/teloxide/payloads/struct.CreateNewStickerSet.html /// [AddStickerToSet]: https://docs.rs/teloxide/latest/teloxide/payloads/struct.AddStickerToSet.html + /// [ReplaceStickerInSet]: https://docs.rs/teloxide/latest/teloxide/payloads/struct.ReplaceStickerInSet.html /// [`File`]: crate::types::File #[derive(Debug, Clone, Serialize)] pub UploadStickerFile (UploadStickerFileSetters) => FileMeta { diff --git a/crates/teloxide-core/src/requests/requester.rs b/crates/teloxide-core/src/requests/requester.rs index a67b3a97..a758f167 100644 --- a/crates/teloxide-core/src/requests/requester.rs +++ b/crates/teloxide-core/src/requests/requester.rs @@ -203,6 +203,20 @@ pub trait Requester { C: Into, F: Into; + type ForwardMessages: Request; + + /// For Telegram documentation see [`ForwardMessages`]. + fn forward_messages( + &self, + chat_id: C, + from_chat_id: F, + message_ids: M, + ) -> Self::ForwardMessages + where + C: Into, + F: Into, + M: IntoIterator; + type CopyMessage: Request; /// For Telegram documentation see [`CopyMessage`]. @@ -216,6 +230,20 @@ pub trait Requester { C: Into, F: Into; + type CopyMessages: Request; + + /// For Telegram documentation see [`CopyMessages`]. + fn copy_messages( + &self, + chat_id: C, + from_chat_id: F, + message_ids: M, + ) -> Self::CopyMessages + where + C: Into, + F: Into, + M: IntoIterator; + type SendPhoto: Request; /// For Telegram documentation see [`SendPhoto`]. @@ -385,6 +413,17 @@ pub trait Requester { where C: Into; + type SetMessageReaction: Request; + + /// For Telegram documentation see [`SetMessageReaction`]. + fn set_message_reaction( + &self, + chat_id: C, + message_id: MessageId, + ) -> Self::SetMessageReaction + where + C: Into; + type GetUserProfilePhotos: Request; /// For Telegram documentation see [`GetUserProfilePhotos`]. @@ -663,7 +702,7 @@ pub trait Requester { &self, chat_id: C, name: N, - icon_color: u32, + icon_color: Rgb, icon_custom_emoji_id: I, ) -> Self::CreateForumTopic where @@ -778,6 +817,13 @@ pub trait Requester { where C: Into; + type GetUserChatBoosts: Request; + + /// For Telegram documentation see [`GetUserChatBoosts`]. + fn get_user_chat_boosts(&self, chat_id: C, user_id: UserId) -> Self::GetUserChatBoosts + where + C: Into; + type SetMyCommands: Request; /// For Telegram documentation see [`SetMyCommands`]. @@ -785,6 +831,14 @@ pub trait Requester { where C: IntoIterator; + type GetBusinessConnection: Request; + + /// For Telegram documentation see [`GetBusinessConnection`]. + fn get_business_connection( + &self, + business_connection_id: BusinessConnectionId, + ) -> Self::GetBusinessConnection; + type GetMyCommands: Request; /// For Telegram documentation see [`GetMyCommands`]. @@ -977,6 +1031,14 @@ pub trait Requester { where C: Into; + type DeleteMessages: Request; + + /// For Telegram documentation see [`DeleteMessages`]. + fn delete_messages(&self, chat_id: C, message_ids: M) -> Self::DeleteMessages + where + C: Into, + M: IntoIterator; + type SendSticker: Request; /// For Telegram documentation see [`SendSticker`]. @@ -1017,7 +1079,6 @@ pub trait Requester { name: N, title: T, stickers: S, - sticker_format: StickerFormat, ) -> Self::CreateNewStickerSet where N: Into, @@ -1054,6 +1115,20 @@ pub trait Requester { where S: Into; + type ReplaceStickerInSet: Request; + + /// For Telegram documentation see [`ReplaceStickerInSet`]. + fn replace_sticker_in_set( + &self, + user_id: UserId, + name: N, + old_sticker: O, + sticker: InputSticker, + ) -> Self::ReplaceStickerInSet + where + N: Into, + O: Into; + type SetStickerSetThumbnail: Request; /// For Telegram documentation see [`SetStickerSetThumbnail`]. @@ -1061,6 +1136,7 @@ pub trait Requester { &self, name: N, user_id: UserId, + format: StickerFormat, ) -> Self::SetStickerSetThumbnail where N: Into; @@ -1249,7 +1325,9 @@ macro_rules! forward_all { delete_webhook, get_webhook_info, forward_message, + forward_messages, copy_message, + copy_messages, send_message, send_photo, send_audio, @@ -1269,6 +1347,7 @@ macro_rules! forward_all { send_poll, send_dice, send_chat_action, + set_message_reaction, get_user_profile_photos, get_file, kick_chat_member, @@ -1313,7 +1392,9 @@ macro_rules! forward_all { unhide_general_forum_topic, unpin_all_general_forum_topic_messages, answer_callback_query, + get_user_chat_boosts, set_my_commands, + get_business_connection, get_my_commands, set_my_name, get_my_name, @@ -1338,6 +1419,7 @@ macro_rules! forward_all { edit_message_reply_markup_inline, stop_poll, delete_message, + delete_messages, send_sticker, get_sticker_set, get_custom_emoji_stickers, @@ -1346,6 +1428,7 @@ macro_rules! forward_all { add_sticker_to_set, set_sticker_position_in_set, delete_sticker_from_set, + replace_sticker_in_set, set_sticker_set_thumbnail, set_custom_emoji_sticker_set_thumbnail, set_sticker_set_title, diff --git a/crates/teloxide-core/src/serde_multipart/mod.rs b/crates/teloxide-core/src/serde_multipart/mod.rs index 0c2966e7..d48380e8 100644 --- a/crates/teloxide-core/src/serde_multipart/mod.rs +++ b/crates/teloxide-core/src/serde_multipart/mod.rs @@ -90,7 +90,7 @@ mod tests { types::{ ChatId, InputFile, InputMedia, InputMediaAnimation, InputMediaAudio, InputMediaDocument, InputMediaPhoto, InputMediaVideo, InputSticker, MessageEntity, - MessageEntityKind, ParseMode, UserId, + MessageEntityKind, ParseMode, StickerFormat, UserId, }, }; @@ -157,6 +157,7 @@ mod tests { emoji_list: vec!["โœˆ๏ธโš™๏ธ".to_owned()], keywords: vec![], mask_position: None, + format: StickerFormat::Static, }, )) .unwrap() @@ -173,8 +174,7 @@ mod tests { .caption_entities(entities()) .thumbnail(InputFile::read( File::open("../../media/teloxide-core-logo.png").await.unwrap(), - )) - .allow_sending_without_reply(true), + )), ) .unwrap() .await; diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index f142825d..3d10253b 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -3,16 +3,29 @@ pub use allowed_update::*; pub use animation::*; pub use audio::*; +pub use birthdate::*; pub use bot_command::*; pub use bot_command_scope::*; pub use bot_description::*; pub use bot_name::*; pub use bot_short_description::*; +pub use business_connection::*; +pub use business_connection_id::*; +pub use business_intro::*; +pub use business_location::*; +pub use business_messages_deleted::*; +pub use business_opening_hours::*; +pub use business_opening_hours_interval::*; pub use callback_game::*; pub use callback_query::*; pub use chat::*; pub use chat_action::*; pub use chat_administrator_rights::*; +pub use chat_boost::*; +pub use chat_boost_added::*; +pub use chat_boost_removed::*; +pub use chat_boost_source::*; +pub use chat_boost_updated::*; pub use chat_full_info::*; pub use chat_invite_link::*; pub use chat_join_request::*; @@ -30,6 +43,7 @@ pub use dice_emoji::*; pub use document::*; pub use encrypted_credentials::*; pub use encrypted_passport_element::*; +pub use external_reply_info::*; pub use file::*; pub use force_reply::*; pub use forum_topic::*; @@ -41,6 +55,11 @@ pub use game::*; pub use game_high_score::*; pub use general_forum_topic_hidden::*; pub use general_forum_topic_unhidden::*; +pub use giveaway::*; +pub use giveaway_completed::*; +pub use giveaway_created::*; +pub use giveaway_winners::*; +pub use inaccessible_message::*; pub use inline_keyboard_button::*; pub use inline_keyboard_markup::*; pub use inline_query::*; @@ -74,17 +93,23 @@ pub use invoice::*; pub use keyboard_button::*; pub use keyboard_button_poll_type::*; pub use keyboard_button_request_chat::*; -pub use keyboard_button_request_user::*; +pub use keyboard_button_request_users::*; pub use label_price::*; +pub use link_preview_options::*; pub use location::*; pub use login_url::*; pub use mask_position::*; +pub use maybe_anonymous_user::*; +pub use maybe_inaccessible_message::*; pub use me::*; pub use menu_button::*; pub use message::*; pub use message_auto_delete_timer_changed::*; pub use message_entity::*; pub use message_id::*; +pub use message_origin::*; +pub use message_reaction_count_updated::*; +pub use message_reaction_updated::*; pub use order_info::*; pub use parse_mode::*; pub use passport_data::*; @@ -96,27 +121,35 @@ pub use poll_answer::*; pub use poll_type::*; pub use pre_checkout_query::*; pub use proximity_alert_triggered::*; +pub use reaction_type::*; pub use reply_keyboard_markup::*; pub use reply_keyboard_remove::*; pub use reply_markup::*; +pub use reply_parameters::*; +pub use request_id::*; pub use response_parameters::*; +pub use rgb::*; pub use sent_web_app_message::*; +pub use shared_user::*; pub use shipping_address::*; pub use shipping_option::*; pub use shipping_query::*; pub use sticker::*; pub use sticker_set::*; pub use story::*; +pub use story_id::*; pub use successful_payment::*; pub use switch_inline_query_chosen_chat::*; pub use target_message::*; +pub use text_quote::*; pub use thread_id::*; pub use unit_false::*; pub use unit_true::*; pub use update::*; pub use user::*; +pub use user_chat_boosts::*; pub use user_profile_photos::*; -pub use user_shared::*; +pub use users_shared::*; pub use venue::*; pub use video::*; pub use video_chat_ended::*; @@ -133,16 +166,28 @@ pub use write_access_allowed::*; mod allowed_update; mod animation; mod audio; +mod birthdate; mod bot_command; mod bot_command_scope; mod bot_description; mod bot_name; mod bot_short_description; +mod business_connection; +mod business_connection_id; +mod business_intro; +mod business_location; +mod business_messages_deleted; +mod business_opening_hours; +mod business_opening_hours_interval; mod callback_game; mod callback_query; mod chat; mod chat_action; mod chat_administrator_rights; +mod chat_boost; +mod chat_boost_removed; +mod chat_boost_source; +mod chat_boost_updated; mod chat_full_info; mod chat_invite_link; mod chat_join_request; @@ -158,6 +203,7 @@ mod contact; mod dice; mod dice_emoji; mod document; +mod external_reply_info; mod file; mod force_reply; mod forum_topic; @@ -169,6 +215,11 @@ mod game; mod game_high_score; mod general_forum_topic_hidden; mod general_forum_topic_unhidden; +mod giveaway; +mod giveaway_completed; +mod giveaway_created; +mod giveaway_winners; +mod inaccessible_message; mod inline_keyboard_button; mod inline_keyboard_markup; mod inline_query_results_button; @@ -180,17 +231,23 @@ mod invoice; mod keyboard_button; mod keyboard_button_poll_type; mod keyboard_button_request_chat; -mod keyboard_button_request_user; +mod keyboard_button_request_users; mod label_price; +mod link_preview_options; mod location; mod login_url; mod mask_position; +mod maybe_anonymous_user; +mod maybe_inaccessible_message; mod me; mod menu_button; mod message; mod message_auto_delete_timer_changed; mod message_entity; mod message_id; +mod message_origin; +mod message_reaction_count_updated; +mod message_reaction_updated; mod order_info; mod parse_mode; mod photo_size; @@ -199,11 +256,16 @@ mod poll_answer; mod poll_type; mod pre_checkout_query; mod proximity_alert_triggered; +mod reaction_type; mod reply_keyboard_markup; mod reply_keyboard_remove; mod reply_markup; +mod reply_parameters; +mod request_id; mod response_parameters; +mod rgb; mod sent_web_app_message; +mod shared_user; mod shipping_address; mod shipping_option; mod shipping_query; @@ -213,13 +275,15 @@ mod story; mod successful_payment; mod switch_inline_query_chosen_chat; mod target_message; +mod text_quote; mod thread_id; mod unit_false; mod unit_true; mod update; mod user; +mod user_chat_boosts; mod user_profile_photos; -mod user_shared; +mod users_shared; mod venue; mod video; mod video_chat_ended; @@ -233,6 +297,7 @@ mod web_app_info; mod webhook_info; mod write_access_allowed; +mod chat_boost_added; mod inline_query; mod inline_query_result; mod inline_query_result_article; @@ -255,6 +320,7 @@ mod inline_query_result_photo; mod inline_query_result_venue; mod inline_query_result_video; mod inline_query_result_voice; +mod story_id; mod encrypted_credentials; mod encrypted_passport_element; @@ -262,10 +328,9 @@ mod passport_data; mod passport_element_error; mod passport_file; -pub use non_telegram_types::{country_code::*, currency::*, until_date::*}; +pub use non_telegram_types::{country_code::*, until_date::*}; mod non_telegram_types { pub(super) mod country_code; - pub(super) mod currency; pub(crate) mod mime; pub(super) mod until_date; } @@ -280,7 +345,11 @@ pub use recipient::*; pub use seconds::*; pub use user_id::*; -use serde::Serialize; +use serde_with::with_prefix; + +// Deserialization prefix for giveaway_message_id field used in GiveawayWinners +// and ChatBoostSourceGiveaway +with_prefix!(prefix_giveaway_message_id "giveaway_"); /// Converts an `i64` timestamp to a `choro::DateTime`, producing serde error /// for invalid timestamps @@ -403,6 +472,45 @@ pub(crate) mod option_url_from_string { } } +// Issue https://github.com/teloxide/teloxide/issues/1135 +// Workaround to avoid flattening with serde-multipart requests (involving +// file-manipulations) +pub(crate) mod msg_id_as_int { + use crate::types::MessageId; + + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + pub(crate) fn serialize(MessageId(id): &MessageId, serializer: S) -> Result + where + S: Serializer, + { + id.serialize(serializer) + } + + pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + i32::deserialize(deserializer).map(MessageId) + } + + #[test] + fn test() { + #[derive(Serialize, Deserialize)] + struct Struct { + #[serde(with = "crate::types::msg_id_as_int")] + message_id: MessageId, + } + + { + let json = r#"{"message_id":123}"#; + let s: Struct = serde_json::from_str(json).unwrap(); + assert_eq!(s.message_id, MessageId(123)); + assert_eq!(serde_json::to_string(&s).unwrap(), json.to_owned()); + } + } +} + pub(crate) mod option_msg_id_as_int { use crate::types::MessageId; @@ -438,77 +546,3 @@ pub(crate) mod option_msg_id_as_int { } } } - -pub(crate) fn serialize_reply_to_message_id( - this: &Option, - serializer: S, -) -> Result -where - S: serde::Serializer, -{ - this.map(|MessageId(id)| id).serialize(serializer) -} - -pub(crate) mod serde_rgb { - use serde::{de::Visitor, Deserializer, Serializer}; - - pub fn serialize(&this: &[u8; 3], s: S) -> Result { - s.serialize_u32(to_u32(this)) - } - - pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[u8; 3], D::Error> { - struct V; - - impl Visitor<'_> for V { - type Value = [u8; 3]; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("an integer represeting an RGB color") - } - - fn visit_u32(self, v: u32) -> Result - where - E: serde::de::Error, - { - Ok(from_u32(v)) - } - - fn visit_u64(self, v: u64) -> Result - 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) - } - - fn to_u32([r, g, b]: [u8; 3]) -> u32 { - u32::from_be_bytes([0, r, g, b]) - } - - fn from_u32(rgb: u32) -> [u8; 3] { - let [_, r, g, b] = rgb.to_be_bytes(); - [r, g, b] - } - - #[test] - fn bytes() { - assert_eq!(to_u32([0xAA, 0xBB, 0xCC]), 0x00AABBCC); - assert_eq!(from_u32(0x00AABBCC), [0xAA, 0xBB, 0xCC]); - } - - #[test] - 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]) - } -} diff --git a/crates/teloxide-core/src/types/allowed_update.rs b/crates/teloxide-core/src/types/allowed_update.rs index 7820ad8f..73f70044 100644 --- a/crates/teloxide-core/src/types/allowed_update.rs +++ b/crates/teloxide-core/src/types/allowed_update.rs @@ -7,6 +7,12 @@ pub enum AllowedUpdate { EditedMessage, ChannelPost, EditedChannelPost, + BusinessConnection, + BusinessMessage, + EditedBusinessMessage, + DeletedBusinessMessages, + MessageReaction, + MessageReactionCount, InlineQuery, ChosenInlineResult, CallbackQuery, @@ -17,4 +23,6 @@ pub enum AllowedUpdate { MyChatMember, ChatMember, ChatJoinRequest, + ChatBoost, + RemovedChatBoost, } diff --git a/crates/teloxide-core/src/types/birthdate.rs b/crates/teloxide-core/src/types/birthdate.rs new file mode 100644 index 00000000..1c7ba65b --- /dev/null +++ b/crates/teloxide-core/src/types/birthdate.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; + +/// Describes the birthdate of a user. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Birthdate { + /// Day of the user's birth; 1-31 + pub day: u8, + + /// Month of the user's birth; 1-12 + pub month: u8, + + /// Year of the user's birth + pub year: Option, +} diff --git a/crates/teloxide-core/src/types/business_connection.rs b/crates/teloxide-core/src/types/business_connection.rs new file mode 100644 index 00000000..5b620d9b --- /dev/null +++ b/crates/teloxide-core/src/types/business_connection.rs @@ -0,0 +1,29 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::{BusinessConnectionId, User, UserId}; + +/// Describes the connection of the bot with a business account. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct BusinessConnection { + /// Unique identifier of the business connection + pub id: BusinessConnectionId, + + /// Business account user that created the business connection + pub user: User, + + /// The user id of the private chat with the user who created the business + /// connection + pub user_chat_id: UserId, + + /// Date the connection was established in Unix time + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub date: DateTime, + + /// `true`, if the bot can act on behalf of the business account in chats + /// that were active in the last 24 hours + pub can_reply: bool, + + /// `true`, if the connection is alive + pub is_enabled: bool, +} diff --git a/crates/teloxide-core/src/types/business_connection_id.rs b/crates/teloxide-core/src/types/business_connection_id.rs new file mode 100644 index 00000000..263e2c02 --- /dev/null +++ b/crates/teloxide-core/src/types/business_connection_id.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; + +/// A unique business connection identifier. +#[derive(Default, Clone, Debug, derive_more::Display, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(transparent)] +pub struct BusinessConnectionId(pub String); + +#[cfg(test)] +mod tests { + use crate::types::BusinessConnectionId; + + #[test] + fn business_connection_id_deser() { + let json = r#""abcd1234""#; + let bcid: BusinessConnectionId = serde_json::from_str(json).unwrap(); + assert_eq!(bcid, BusinessConnectionId(String::from("abcd1234"))); + } + + #[test] + fn business_connection_id_ser() { + let bcid: BusinessConnectionId = BusinessConnectionId(String::from("abcd1234")); + let json = serde_json::to_string(&bcid).unwrap(); + assert_eq!(json, r#""abcd1234""#); + } +} diff --git a/crates/teloxide-core/src/types/business_intro.rs b/crates/teloxide-core/src/types/business_intro.rs new file mode 100644 index 00000000..c4684211 --- /dev/null +++ b/crates/teloxide-core/src/types/business_intro.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::Sticker; + +/// An introduction for a Business. +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct BusinessIntro { + /// Title text of the business intro + pub title: Option, + + /// Message text of the business intro + pub message: Option, + + /// Sticker of the business intro + pub sticker: Option, +} diff --git a/crates/teloxide-core/src/types/business_location.rs b/crates/teloxide-core/src/types/business_location.rs new file mode 100644 index 00000000..514fe581 --- /dev/null +++ b/crates/teloxide-core/src/types/business_location.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::Location; + +/// Details about the location of a Business +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct BusinessLocation { + /// Address of the business. + pub address: String, + + /// Location of the business. + pub location: Option, +} diff --git a/crates/teloxide-core/src/types/business_messages_deleted.rs b/crates/teloxide-core/src/types/business_messages_deleted.rs new file mode 100644 index 00000000..3c84daea --- /dev/null +++ b/crates/teloxide-core/src/types/business_messages_deleted.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{BusinessConnectionId, Chat, MessageId}; + +/// This object is received when messages are deleted from a connected business +/// account. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct BusinessMessagesDeleted { + /// Unique identifier of the business connection. + pub business_connection_id: BusinessConnectionId, + + /// Information about a chat in the business account. The bot may not have + /// access to the chat or the corresponding user. + pub chat: Chat, + + /// The list of identifiers of deleted messages in the chat of the business + /// account. + pub message_ids: Vec, +} diff --git a/crates/teloxide-core/src/types/business_opening_hours.rs b/crates/teloxide-core/src/types/business_opening_hours.rs new file mode 100644 index 00000000..f6bac2b9 --- /dev/null +++ b/crates/teloxide-core/src/types/business_opening_hours.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::BusinessOpeningHoursInterval; + +/// Details about the opening hours of a Business. +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct BusinessOpeningHours { + /// Unique name of the time zone for which the opening hours are defined. + pub time_zone_name: String, + + /// List of time intervals describing business opening hours. + pub opening_hours: Vec, +} diff --git a/crates/teloxide-core/src/types/business_opening_hours_interval.rs b/crates/teloxide-core/src/types/business_opening_hours_interval.rs new file mode 100644 index 00000000..3c966928 --- /dev/null +++ b/crates/teloxide-core/src/types/business_opening_hours_interval.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; + +/// Time intervals used to describe the opening hours of a Business. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct BusinessOpeningHoursInterval { + /// The minute's sequence number in a week, starting on Monday, marking the + /// start of the time interval during which the business is open; + /// 0 - 7 * 24* 60 + pub opening_minute: u16, + + /// The minute's sequence number in a week, starting on Monday, marking the + /// end of the time interval during which the business is open; + /// 0 - 8 * 24* 60 + pub closing_minute: u16, +} diff --git a/crates/teloxide-core/src/types/callback_query.rs b/crates/teloxide-core/src/types/callback_query.rs index bb765aa0..4ab58252 100644 --- a/crates/teloxide-core/src/types/callback_query.rs +++ b/crates/teloxide-core/src/types/callback_query.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::types::{Message, User}; +use crate::types::{MaybeInaccessibleMessage, Message, User}; /// This object represents an incoming callback query from a callback button in /// an [inline keyboard]. @@ -24,10 +24,12 @@ pub struct CallbackQuery { /// A sender. pub from: User, - /// A message with the callback button that originated the query. Note that - /// message content and message date will not be available if the message - /// is too old. - pub message: Option, + /// Message sent by the bot with the callback button that originated the + /// query. + /// + /// Note: if the message is too old, it will be + /// [`MaybeInaccessibleMessage::Inaccessible`]. + pub message: Option, /// An identifier of the message sent via the bot in inline mode, that /// originated the query. @@ -58,7 +60,15 @@ impl CallbackQuery { use crate::util::flatten; use std::iter::once; - once(&self.from).chain(flatten(self.message.as_ref().map(Message::mentioned_users))) + once(&self.from).chain(flatten(self.regular_message().map(Message::mentioned_users))) + } + + #[must_use] + pub fn regular_message(&self) -> Option<&Message> { + self.message + .as_ref() + // If we can access the message + .and_then(|maybe| maybe.regular_message()) } } diff --git a/crates/teloxide-core/src/types/chat.rs b/crates/teloxide-core/src/types/chat.rs index 3f6fbd52..f9e94d9c 100644 --- a/crates/teloxide-core/src/types/chat.rs +++ b/crates/teloxide-core/src/types/chat.rs @@ -1,7 +1,8 @@ use serde::{Deserialize, Serialize}; use crate::types::{ - ChatFullInfo, ChatId, ChatLocation, ChatPermissions, ChatPhoto, Message, Seconds, True, User, + Birthdate, BusinessIntro, BusinessLocation, BusinessOpeningHours, ChatFullInfo, ChatId, + ChatLocation, ChatPermissions, ChatPhoto, Message, ReactionType, Seconds, True, User, }; /// This object represents a chat. @@ -21,6 +22,12 @@ pub struct Chat { /// [`GetChat`]: crate::payloads::GetChat pub photo: Option, + /// List of available reactions allowed in the chat. If omitted, then all + /// emoji reactions are allowed. Returned only from [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub available_reactions: Option>, + /// The most recent pinned message (by sending date). Returned only in /// [`GetChat`]. /// @@ -56,7 +63,7 @@ pub struct Chat { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum ChatKind { - Public(ChatPublic), + Public(Box), Private(ChatPrivate), } @@ -108,13 +115,6 @@ pub struct ChatPrivate { /// A last name of the other party in a private chat. pub last_name: Option, - /// Custom emoji identifier of emoji status of the other party in a private - /// chat. Returned only in [`GetChat`]. - /// - /// [`GetChat`]: crate::payloads::GetChat - // FIXME: CustomEmojiId - pub emoji_status_custom_emoji_id: Option, - /// Bio of the other party in a private chat. Returned only in [`GetChat`]. /// /// [`GetChat`]: crate::payloads::GetChat @@ -133,6 +133,36 @@ pub struct ChatPrivate { /// /// [`GetChat`]: crate::payloads::GetChat pub has_restricted_voice_and_video_messages: Option, + + /// For private chats, the personal channel of the user. Returned only in + /// [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub personal_chat: Option>, + + /// For private chats, the date of birth of the user. Returned only in + /// [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub birthdate: Option, + + /// For private chats with business accounts, the intro of the business. + /// Returned only in [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub business_intro: Option, + + /// For private chats with business accounts, the location of the business. + /// Returned only in [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub business_location: Option, + + /// For private chats with business accounts, the opening hours of the + /// business. Returned only in [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub business_opening_hours: Option, } #[serde_with::skip_serializing_none] @@ -197,6 +227,13 @@ pub struct PublicChatSupergroup { /// [`GetChat`]: crate::payloads::GetChat pub can_set_sticker_set: Option, + /// For supergroups, the name of the group's custom emoji sticker set. + /// Custom emoji from this set can be used by all users and bots in the + /// group. Returned only from [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub custom_emoji_sticker_set_name: Option, + /// A default chat member permissions, for groups and supergroups. /// Returned only from [`GetChat`]. /// @@ -209,6 +246,13 @@ pub struct PublicChatSupergroup { /// [`GetChat`]: crate::payloads::GetChat pub slow_mode_delay: Option, + /// For supergroups, the minimum number of boosts that a non-administrator + /// user needs to add in order to ignore slow mode and chat permissions. + /// Returned only from [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub unrestrict_boost_count: Option, + /// Unique identifier for the linked chat, i.e. the discussion group /// identifier for a channel and vice versa. Returned only in [`GetChat`]. /// @@ -242,20 +286,29 @@ impl Chat { #[must_use] pub fn is_group(&self) -> bool { - matches!(self.kind, ChatKind::Public(ChatPublic { kind: PublicChatKind::Group(_), .. })) + if let ChatKind::Public(chat_pub) = &self.kind { + matches!(**chat_pub, ChatPublic { kind: PublicChatKind::Group(_), .. }) + } else { + false + } } #[must_use] pub fn is_supergroup(&self) -> bool { - matches!( - self.kind, - ChatKind::Public(ChatPublic { kind: PublicChatKind::Supergroup(_), .. }) - ) + if let ChatKind::Public(chat_pub) = &self.kind { + matches!(**chat_pub, ChatPublic { kind: PublicChatKind::Supergroup(_), .. }) + } else { + false + } } #[must_use] pub fn is_channel(&self) -> bool { - matches!(self.kind, ChatKind::Public(ChatPublic { kind: PublicChatKind::Channel(_), .. })) + if let ChatKind::Public(chat_pub) = &self.kind { + matches!(**chat_pub, ChatPublic { kind: PublicChatKind::Channel(_), .. }) + } else { + false + } } #[must_use] @@ -355,6 +408,22 @@ impl Chat { None } + /// For supergroups, the name of the group's custom emoji sticker set. + /// Custom emoji from this set can be used by all users and bots in the + /// group. Returned only from [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + #[must_use] + pub fn custom_emoji_sticker_set_name(&self) -> Option<&str> { + if let ChatKind::Public(this) = &self.kind { + if let PublicChatKind::Supergroup(this) = &this.kind { + return this.custom_emoji_sticker_set_name.as_deref(); + } + } + + None + } + /// The minimum allowed delay between consecutive messages sent by each /// unpriviledged user. Returned only from [`GetChat`]. /// @@ -370,6 +439,21 @@ impl Chat { None } + /// Unique identifier for the linked chat, i.e. the discussion group + /// identifier for a channel and vice versa. Returned only in [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + #[must_use] + pub fn unrestrict_boost_count(&self) -> Option { + if let ChatKind::Public(this) = &self.kind { + if let PublicChatKind::Supergroup(this) = &this.kind { + return this.unrestrict_boost_count; + } + } + + None + } + /// The location to which the supergroup is connected. Returned only in /// [`GetChat`]. /// @@ -518,7 +602,7 @@ impl Chat { } mod serde_helper { - use crate::types::True; + use crate::types::{Birthdate, BusinessIntro, BusinessLocation, BusinessOpeningHours, True}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] @@ -539,7 +623,11 @@ mod serde_helper { bio: Option, has_private_forwards: Option, has_restricted_voice_and_video_messages: Option, - emoji_status_custom_emoji_id: Option, + personal_chat: Option>, + birthdate: Option, + business_intro: Option, + business_location: Option, + business_opening_hours: Option, } impl From for super::ChatPrivate { @@ -552,7 +640,11 @@ mod serde_helper { bio, has_private_forwards, has_restricted_voice_and_video_messages, - emoji_status_custom_emoji_id, + personal_chat, + birthdate, + business_intro, + business_location, + business_opening_hours, }: ChatPrivate, ) -> Self { Self { @@ -562,7 +654,11 @@ mod serde_helper { bio, has_private_forwards, has_restricted_voice_and_video_messages, - emoji_status_custom_emoji_id, + personal_chat, + birthdate, + business_intro, + business_location, + business_opening_hours, } } } @@ -576,7 +672,11 @@ mod serde_helper { bio, has_private_forwards, has_restricted_voice_and_video_messages, - emoji_status_custom_emoji_id, + personal_chat, + birthdate, + business_intro, + business_location, + business_opening_hours, }: super::ChatPrivate, ) -> Self { Self { @@ -587,7 +687,11 @@ mod serde_helper { bio, has_private_forwards, has_restricted_voice_and_video_messages, - emoji_status_custom_emoji_id, + personal_chat, + birthdate, + business_intro, + business_location, + business_opening_hours, } } } @@ -603,7 +707,7 @@ mod tests { fn channel_de() { let expected = Chat { id: ChatId(-1), - kind: ChatKind::Public(ChatPublic { + kind: ChatKind::Public(Box::new(ChatPublic { title: None, kind: PublicChatKind::Channel(PublicChatChannel { username: Some("channel_name".into()), @@ -612,15 +716,29 @@ mod tests { description: None, invite_link: None, has_protected_content: None, - }), + })), photo: None, + available_reactions: Some(vec![ReactionType::Emoji { emoji: "๐ŸŒญ".to_owned() }]), pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None }, + chat_full_info: ChatFullInfo::default(), }; - let actual = from_str(r#"{"id":-1,"type":"channel","username":"channel_name"}"#).unwrap(); + let actual = from_str( + r#"{ + "id": -1, + "type": "channel", + "username": "channel_name", + "available_reactions": [ + { + "type": "emoji", + "emoji": "๐ŸŒญ" + } + ] + }"#, + ) + .unwrap(); assert_eq!(expected, actual); } @@ -636,17 +754,35 @@ mod tests { bio: None, has_private_forwards: None, has_restricted_voice_and_video_messages: None, - emoji_status_custom_emoji_id: None + personal_chat: None, + birthdate: None, + business_intro: None, + business_location: None, + business_opening_hours: None, }), photo: None, + available_reactions: Some(vec![ReactionType::Emoji { emoji: "๐ŸŒญ".to_owned() }]), pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None } + chat_full_info: ChatFullInfo::default() }, - from_str(r#"{"id":0,"type":"private","username":"username","first_name":"Anon"}"#) - .unwrap() + from_str( + r#"{ + "id": 0, + "type": "private", + "username": "username", + "first_name": "Anon", + "available_reactions": [ + { + "type": "emoji", + "emoji": "๐ŸŒญ" + } + ] + }"# + ) + .unwrap() ); } @@ -661,14 +797,19 @@ mod tests { bio: None, has_private_forwards: None, has_restricted_voice_and_video_messages: None, - emoji_status_custom_emoji_id: None, + personal_chat: None, + birthdate: None, + business_intro: None, + business_location: None, + business_opening_hours: None, }), photo: None, + available_reactions: None, pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None }, + chat_full_info: ChatFullInfo::default(), }; let json = to_string(&chat).unwrap(); diff --git a/crates/teloxide-core/src/types/chat_administrator_rights.rs b/crates/teloxide-core/src/types/chat_administrator_rights.rs index c3e9c62c..3a80c8c6 100644 --- a/crates/teloxide-core/src/types/chat_administrator_rights.rs +++ b/crates/teloxide-core/src/types/chat_administrator_rights.rs @@ -47,16 +47,13 @@ pub struct ChatAdministratorRights { /// supergroups only pub can_pin_messages: Option, - /// `true`, if the administrator can post stories in the channel; - /// channels only + /// `true`, if the administrator can post stories to the chat pub can_post_stories: Option, - /// `true`, if the administrator can edit stories posted by other users; - /// channels only + /// `true`, if the administrator can edit stories posted by other users pub can_edit_stories: Option, - /// `true`, if the administrator can delete stories posted by other users; - /// channels only + /// `true`, if the administrator can delete stories posted by other users pub can_delete_stories: Option, /// `true`, if the user is allowed to create, rename, close, and reopen diff --git a/crates/teloxide-core/src/types/chat_boost.rs b/crates/teloxide-core/src/types/chat_boost.rs new file mode 100644 index 00000000..06e10c07 --- /dev/null +++ b/crates/teloxide-core/src/types/chat_boost.rs @@ -0,0 +1,52 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::ChatBoostSource; + +/// This object contains information about a chat boost. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ChatBoost { + /// Unique identifier of the boost. + pub boost_id: String, + + /// Point in time (Unix timestamp) when the chat was boosted. + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub add_date: DateTime, + + /// Point in time (Unix timestamp) when the boost will automatically expire, + /// unless the booster's Telegram Premium subscription is prolonged. + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub expiration_date: DateTime, + + /// Source of the added boost. + pub source: ChatBoostSource, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "boost_id": "4506e1b7e866e33fcbde78fe1746ec3a", + "add_date": 1721399621, + "expiration_date": 1745088963, + "source": { + "source": "premium", + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + } + } + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/chat_boost_added.rs b/crates/teloxide-core/src/types/chat_boost_added.rs new file mode 100644 index 00000000..1f37dccf --- /dev/null +++ b/crates/teloxide-core/src/types/chat_boost_added.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + +/// This object represents a service message about a user boosting a chat. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ChatBoostAdded { + /// Number of boosts added by the user + pub boost_count: u16, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "boost_count": 4 + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/chat_boost_removed.rs b/crates/teloxide-core/src/types/chat_boost_removed.rs new file mode 100644 index 00000000..1be28960 --- /dev/null +++ b/crates/teloxide-core/src/types/chat_boost_removed.rs @@ -0,0 +1,55 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::{Chat, ChatBoostSource}; + +/// This object represents a boost removed from a chat. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ChatBoostRemoved { + /// Chat which was boosted + pub chat: Chat, + + // FIXME: BoostId + /// Unique identifier of the boost + pub boost_id: String, + + /// Point in time (Unix timestamp) when the boost was removed + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub remove_date: DateTime, + + /// Source of the removed boost + pub source: ChatBoostSource, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "boost_id": "4506e1b7e866e33fcbde78fe1746ec3a", + "remove_date": 1745089963, + "source": { + "source": "premium", + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + } + } + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/chat_boost_source.rs b/crates/teloxide-core/src/types/chat_boost_source.rs new file mode 100644 index 00000000..561c0ae5 --- /dev/null +++ b/crates/teloxide-core/src/types/chat_boost_source.rs @@ -0,0 +1,124 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{MessageId, User}; + +/// This object describes the source of a chat boost. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(tag = "source")] +pub enum ChatBoostSource { + Premium(ChatBoostSourcePremium), + GiftCode(ChatBoostSourceGiftCode), + Giveaway(ChatBoostSourceGiveaway), +} + +/// The boost was obtained by subscribing to Telegram Premium or by gifting a +/// Telegram Premium subscription to another user. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ChatBoostSourcePremium { + /// User that boosted the chat. + pub user: User, +} + +/// The boost was obtained by the creation of Telegram Premium gift codes to +/// boost a chat. Each such code boosts the chat 4 times for the duration of the +/// corresponding Telegram Premium subscription. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ChatBoostSourceGiftCode { + /// User for which the gift code was created. + pub user: User, +} + +/// The boost was obtained by the creation of a Telegram Premium giveaway. This +/// boosts the chat 4 times for the duration of the corresponding Telegram +/// Premium subscription. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ChatBoostSourceGiveaway { + /// Identifier of a message in the chat with the giveaway; the message could + /// have been deleted already. May be 0 if the message isn't sent yet. + #[serde(flatten, with = "crate::types::prefix_giveaway_message_id")] + pub giveaway_message_id: MessageId, + + /// User that won the prize in the giveaway if any. + pub user: Option, + + /// `true`, if the giveaway was completed, but there was no user to win the + /// prize. + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub is_unclaimed: bool, +} + +impl ChatBoostSource { + #[must_use] + pub fn user(&self) -> Option<&User> { + Some(match &self { + Self::Premium(premium) => &premium.user, + Self::GiftCode(gift_code) => &gift_code.user, + Self::Giveaway(giveaway) => return giveaway.user.as_ref(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize_premium() { + let data = r#" + { + "source": "premium", + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + } + } + "#; + serde_json::from_str::(data).unwrap(); + } + + #[test] + fn deserialize_gift_code() { + let data = r#" + { + "source": "gift_code", + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": false + } + } + "#; + serde_json::from_str::(data).unwrap(); + } + + #[test] + fn deserialize_giveaway() { + let data = r#" + { + "source": "giveaway", + "giveaway_message_id": 420, + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": false + } + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/chat_boost_updated.rs b/crates/teloxide-core/src/types/chat_boost_updated.rs new file mode 100644 index 00000000..b4646b5f --- /dev/null +++ b/crates/teloxide-core/src/types/chat_boost_updated.rs @@ -0,0 +1,49 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{Chat, ChatBoost}; + +/// This object represents a boost added to a chat or changed. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ChatBoostUpdated { + /// Chat which was boosted + pub chat: Chat, + + /// Infomation about the chat boost + pub boost: ChatBoost, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "boost": { + "boost_id": "4506e1b7e866e33fcbde78fe1746ec3a", + "add_date": 1721399621, + "expiration_date": 1745088963, + "source": { + "source": "premium", + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + } + } + } + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/chat_full_info.rs b/crates/teloxide-core/src/types/chat_full_info.rs index 69d547a7..f48f3d07 100644 --- a/crates/teloxide-core/src/types/chat_full_info.rs +++ b/crates/teloxide-core/src/types/chat_full_info.rs @@ -4,12 +4,43 @@ use serde::{Deserialize, Serialize}; // TODO: in the TBA7.3 the Chat will be splitted into Chat and ChatInfo // Currently it's just a container for the some fields of the Chat struct #[serde_with::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ChatFullInfo { + /// Identifier of the accent color for the chat name and backgrounds of the + /// chat photo, reply header, and link preview. See [accent colors] for more + /// details. + /// + /// [accent colors]: https://core.telegram.org/bots/api#accent-colors + pub accent_color_id: Option, + /// Custom emoji identifier of the emoji chosen by the chat for the reply + /// header and link preview background + // FIXME: CustomEmojiId + pub background_custom_emoji_id: Option, + /// Identifier of the accent color for the chat's profile background. See + /// [profile accent colors] for more details. + /// + /// [profile accent colors]: https://core.telegram.org/bots/api#profile-accent-colors + pub profile_accent_color_id: Option, + /// Custom emoji identifier of the emoji chosen by the chat for its profile + /// background + // FIXME: CustomEmojiId + pub profile_background_custom_emoji_id: Option, + /// Custom emoji identifier of emoji status of the other party in a private + /// chat. Returned only in [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + // FIXME: CustomEmojiId + pub emoji_status_custom_emoji_id: Option, /// Expiration date of the emoji status of the chat or the other party in a /// private chat, in Unix time, if any #[serde(default, with = "crate::types::serde_opt_date_from_unix_timestamp")] pub emoji_status_expiration_date: Option>, + /// True, if new chat members will have access to old messages; available + /// only to chat administrators. Returned only in [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + #[serde(default)] + pub has_visible_history: bool, } #[cfg(test)] @@ -18,18 +49,18 @@ mod tests { #[test] fn test_chat_full_info_de() { - assert_eq!( - serde_json::from_str::("{}").unwrap(), - ChatFullInfo { emoji_status_expiration_date: None } - ); + assert_eq!(serde_json::from_str::("{}").unwrap(), ChatFullInfo::default()); assert_eq!( serde_json::from_str::( r#"{ - "emoji_status_expiration_date": 1720708004 - }"# + "emoji_status_expiration_date": 1720708004 + }"# ) .unwrap(), - ChatFullInfo { emoji_status_expiration_date: DateTime::from_timestamp(1720708004, 0) } + ChatFullInfo { + emoji_status_expiration_date: DateTime::from_timestamp(1720708004, 0), + ..ChatFullInfo::default() + } ); } } diff --git a/crates/teloxide-core/src/types/chat_member.rs b/crates/teloxide-core/src/types/chat_member.rs index 5aec15ee..458474f7 100644 --- a/crates/teloxide-core/src/types/chat_member.rs +++ b/crates/teloxide-core/src/types/chat_member.rs @@ -80,18 +80,15 @@ pub struct Administrator { /// `true` if the administrator can delete messages of other users. pub can_delete_messages: bool, - /// `true` if the administrator can post stories in the channel, channels - /// only. + /// `true` if the administrator can post stories to the chat. #[serde(default)] pub can_post_stories: bool, - /// `true` if the administrator can edit stories posted by other users, - /// channels only. + /// `true` if the administrator can edit stories posted by other users. #[serde(default)] pub can_edit_stories: bool, - /// `true` if the administrator can delete stories posted by other users, - /// channels only. + /// `true` if the administrator can delete stories posted by other users. #[serde(default)] pub can_delete_stories: bool, @@ -447,8 +444,7 @@ impl ChatMemberKind { } } - /// Returns `true` if the user can post stories in the channel, channels - /// only. + /// Returns `true` if the administrator can post stories to the chat. /// /// I.e. returns `true` if the user /// - is the owner of the chat (even if the chat is not a channel) @@ -467,8 +463,8 @@ impl ChatMemberKind { } } - /// Returns `true` if the user can edit stories posted by other users, - /// channels only. + /// Returns `true` if the administrator can edit stories posted by other + /// users. /// /// I.e. returns `true` if the user /// - is the owner of the chat (even if the chat is not a channel) @@ -487,8 +483,8 @@ impl ChatMemberKind { } } - /// Returns `true` if the user can delete stories posted by other users, - /// channels only. + /// Returns `true` if the administrator can delete stories posted by other + /// users. /// /// I.e. returns `true` if the user /// - is the owner of the chat diff --git a/crates/teloxide-core/src/types/chat_shared.rs b/crates/teloxide-core/src/types/chat_shared.rs index 4c75b94e..4616190a 100644 --- a/crates/teloxide-core/src/types/chat_shared.rs +++ b/crates/teloxide-core/src/types/chat_shared.rs @@ -1,15 +1,25 @@ use serde::{Deserialize, Serialize}; -use crate::types::ChatId; +use crate::types::{ChatId, PhotoSize, RequestId}; -/// Information about the chat whose identifier was shared with the bot using a +/// Information about a chat that was shared with the bot using a /// [`KeyboardButtonRequestChat`] button. /// /// [`KeyboardButtonRequestChat`]: crate::types::KeyboardButtonRequestChat #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct ChatShared { /// Identifier of the request. - pub request_id: i32, + pub request_id: RequestId, + /// Identifier of the shared chat. pub chat_id: ChatId, + + /// Title of the chat, if it was requested. + pub title: Option, + + /// Username of the chat, if it was requested. + pub username: Option, + + /// Available sizes of the chat photo, if it was requested. + pub photo: Option>, } diff --git a/crates/teloxide-core/src/types/external_reply_info.rs b/crates/teloxide-core/src/types/external_reply_info.rs new file mode 100644 index 00000000..14c89ae3 --- /dev/null +++ b/crates/teloxide-core/src/types/external_reply_info.rs @@ -0,0 +1,62 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{ + Animation, Audio, Chat, Contact, Dice, Document, Game, Giveaway, GiveawayWinners, Invoice, + LinkPreviewOptions, Location, MessageId, MessageOrigin, PhotoSize, Poll, Sticker, Story, Venue, + Video, VideoNote, Voice, +}; + +/// This object contains information about a message that is being replied to, +/// which may come from another chat or forum topic. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ExternalReplyInfo { + /// Origin of the message replied to by the given message. + pub origin: MessageOrigin, + /// Chat the original message belongs to. Available only if the chat is a + /// supergroup or a channel. + pub chat: Option, + /// Unique message identifier inside the original chat. Available only if + /// the original chat is a supergroup or a channel. + #[serde(with = "crate::types::option_msg_id_as_int")] + pub message_id: Option, + /// Options used for link preview generation for the original message, if it + /// is a text message. + pub link_preview_options: Option, + /// _true_, if the message media is covered by a spoiler animation. + #[serde(default)] + pub has_media_spoiler: bool, + + #[serde(flatten)] + pub kind: ExternalReplyInfoKind, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ExternalReplyInfoKind { + // Note: + // - `Venue` must be in front of `Location` + // - `Animation` must be in front of `Document` + // + // This is needed so serde doesn't parse `Venue` as `Location` or `Animation` as `Document` + // (for backward compatability telegram duplicates some fields). + // + // See + Animation(Animation), + Audio(Audio), + Contact(Contact), + Dice(Dice), + Document(Document), + Game(Game), + Venue(Venue), + Location(Location), + Photo(Vec), + Poll(Poll), + Sticker(Sticker), + Story(Story), + Giveaway(Giveaway), + GiveawayWinners(GiveawayWinners), + Video(Video), + VideoNote(VideoNote), + Voice(Voice), + Invoice(Invoice), +} diff --git a/crates/teloxide-core/src/types/force_reply.rs b/crates/teloxide-core/src/types/force_reply.rs index 60f4130d..65ae3be6 100644 --- a/crates/teloxide-core/src/types/force_reply.rs +++ b/crates/teloxide-core/src/types/force_reply.rs @@ -29,7 +29,7 @@ pub struct ForceReply { /// (has reply_to_message_id), sender of the original message. /// /// [`Message`]: crate::types::Message - #[serde(skip_serializing_if = "std::ops::Not::not")] + #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub selective: bool, } @@ -54,3 +54,20 @@ impl ForceReply { Self { selective: true, ..self } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "force_reply": true, + "input_field_placeholder": "placeholder", + "selective": false + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/forum_topic.rs b/crates/teloxide-core/src/types/forum_topic.rs index 1eae5e8e..50228823 100644 --- a/crates/teloxide-core/src/types/forum_topic.rs +++ b/crates/teloxide-core/src/types/forum_topic.rs @@ -1,7 +1,7 @@ -use crate::types::ThreadId; - use serde::{Deserialize, Serialize}; +use crate::types::{Rgb, ThreadId}; + /// This object represents a forum topic. /// /// [The official docs](https://core.telegram.org/bots/api#forumtopiccreated). @@ -16,9 +16,7 @@ pub struct ForumTopic { pub name: String, /// Color of the topic icon in RGB format. - // FIXME: use/add a specialized rgb color type? - #[serde(with = "crate::types::serde_rgb")] - pub icon_color: [u8; 3], + pub icon_color: Rgb, /// Unique identifier of the custom emoji shown as the topic icon. // FIXME: CustomEmojiId diff --git a/crates/teloxide-core/src/types/forum_topic_created.rs b/crates/teloxide-core/src/types/forum_topic_created.rs index b7f04344..02662649 100644 --- a/crates/teloxide-core/src/types/forum_topic_created.rs +++ b/crates/teloxide-core/src/types/forum_topic_created.rs @@ -1,5 +1,7 @@ use serde::{Deserialize, Serialize}; +use crate::types::Rgb; + /// This object represents a service message about a new forum topic created in /// the chat. /// @@ -11,9 +13,7 @@ pub struct ForumTopicCreated { pub name: String, /// Color of the topic icon in RGB format. - // FIXME: use/add a specialized rgb color type? - #[serde(with = "crate::types::serde_rgb")] - pub icon_color: [u8; 3], + pub icon_color: Rgb, /// Unique identifier of the custom emoji shown as the topic icon. // FIXME: CustomEmojiId @@ -22,7 +22,7 @@ pub struct ForumTopicCreated { #[cfg(test)] mod tests { - use crate::types::ForumTopicCreated; + use super::*; #[test] fn deserialization() { @@ -32,7 +32,7 @@ mod tests { let event = serde_json::from_str::(json).unwrap(); assert_eq!(event.name, "???"); - assert_eq!(event.icon_color, [0x8E, 0xEE, 0x98]); + assert_eq!(event.icon_color, Rgb { r: 0x8E, g: 0xEE, b: 0x98 }); assert_eq!(event.icon_custom_emoji_id.as_deref(), Some("5312536423851630001")); } } diff --git a/crates/teloxide-core/src/types/giveaway.rs b/crates/teloxide-core/src/types/giveaway.rs new file mode 100644 index 00000000..91306106 --- /dev/null +++ b/crates/teloxide-core/src/types/giveaway.rs @@ -0,0 +1,70 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::{Chat, CountryCode}; + +/// This object represents a message about a scheduled giveaway. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Giveaway { + /// The list of chats which the user must join to participate in the + /// giveaway. + pub chats: Vec, + + /// Point in time (Unix timestamp) when winners of the giveaway will be + /// selected + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub winners_selection_date: DateTime, + + /// The number of users which are supposed to be selected as winners of the + /// giveaway + pub winner_count: u32, + + /// `true`, if only users who join the chats after the giveaway started + /// should be eligible to win + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub only_new_members: bool, + + /// `true`, if the list of giveaway winners will be visible to everyone + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub has_public_winners: bool, + + /// Description of additional giveaway prize + pub prize_description: Option, + + /// A list of two-letter [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country codes indicating the + /// countries from which eligible users for the giveaway must come. If + /// empty, then all users can participate in the giveaway. Users with a + /// phone number that was bought on Fragment can always participate in + /// giveaways. + pub country_codes: Option>, + + /// The number of months the Telegram Premium subscription won from the + /// giveaway will be active for + pub premium_subscription_month_count: Option, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "chats": [ + { + "id": -1002236736395, + "title": "Test", + "type": "channel" + } + ], + "winners_selection_date": 1721162701, + "winner_count": 1, + "has_public_winners": true, + "premium_subscription_month_count": 6 + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/giveaway_completed.rs b/crates/teloxide-core/src/types/giveaway_completed.rs new file mode 100644 index 00000000..888395e4 --- /dev/null +++ b/crates/teloxide-core/src/types/giveaway_completed.rs @@ -0,0 +1,61 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::Message; + +/// This object represents a service message about the completion of a giveaway +/// without public winners. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct GiveawayCompleted { + /// Number of winners in the giveaway + pub winner_count: u32, + + /// Number of undistributed prizes + pub unclaimed_prize_count: Option, + + /// Message with the giveaway that was completed, if it wasn't deleted + pub giveaway_message: Option>, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "winner_count": 0, + "unclaimed_prize_count": 1, + "giveaway_message": { + "message_id": 24, + "sender_chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "date": 1721161230, + "giveaway": { + "chats": [ + { + "id": -1002236736395, + "title": "Test", + "type": "channel" + } + ], + "winners_selection_date": 1721162701, + "winner_count": 1, + "has_public_winners": true, + "premium_subscription_month_count": 6 + } + } + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/giveaway_created.rs b/crates/teloxide-core/src/types/giveaway_created.rs new file mode 100644 index 00000000..de894c02 --- /dev/null +++ b/crates/teloxide-core/src/types/giveaway_created.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +/// This object represents a service message about the creation of a scheduled +/// giveaway. Currently holds no information. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct GiveawayCreated {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#"{}"#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/giveaway_winners.rs b/crates/teloxide-core/src/types/giveaway_winners.rs new file mode 100644 index 00000000..b5043ad8 --- /dev/null +++ b/crates/teloxide-core/src/types/giveaway_winners.rs @@ -0,0 +1,83 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::{Chat, MessageId, User}; + +/// This object represents a message about the completion of a giveaway with +/// public winners. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct GiveawayWinners { + /// The chat that created the giveaway + pub chat: Chat, + + /// Identifier of the messsage with the giveaway in the chat + #[serde(flatten, with = "crate::types::prefix_giveaway_message_id")] + pub giveaway_message_id: MessageId, + + /// Point in time (Unix timestamp) when winners of the giveaway were + /// selected + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub winners_selection_date: DateTime, + + /// Total number of winners in the giveaway + pub winner_count: u32, + + /// List of up to 100 winners of the giveaway + pub winners: Vec, + + /// The number of other chats the user had to join in order to be eligible + /// for the giveaway + pub additional_chat_count: Option, + + /// The number of months the Telegram Premium subscription won from the + /// giveaway will be active for + pub premium_subscription_month_count: Option, + + /// Number of undistributed prizes + pub unclaimed_prize_count: Option, + + /// `true`, if only users who had joined the chats after the giveaway + /// started were eligible to win + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub only_new_members: bool, + + /// `true`, if the giveaway was canceled because the payment for it was + /// refunded + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub was_refunded: bool, + + /// Description of additional giveaway prize + pub prize_description: Option, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "giveaway_message_id": 27, + "winners_selection_date": 1721162701, + "premium_subscription_month_count": 6, + "winner_count": 1, + "winners": [ + { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10" + } + ] + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/inaccessible_message.rs b/crates/teloxide-core/src/types/inaccessible_message.rs new file mode 100644 index 00000000..9302d493 --- /dev/null +++ b/crates/teloxide-core/src/types/inaccessible_message.rs @@ -0,0 +1,14 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{Chat, MessageId}; + +/// This object describes a message that was deleted or is otherwise +/// inaccessible to the bot. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct InaccessibleMessage { + /// Chat the message belonged to + pub chat: Chat, + /// Unique message identifier inside the chat + #[serde(flatten)] + pub message_id: MessageId, +} diff --git a/crates/teloxide-core/src/types/inline_query_result.rs b/crates/teloxide-core/src/types/inline_query_result.rs index 4a4c07b9..45f658ff 100644 --- a/crates/teloxide-core/src/types/inline_query_result.rs +++ b/crates/teloxide-core/src/types/inline_query_result.rs @@ -266,7 +266,7 @@ mod tests { InlineQueryResultGif, InlineQueryResultLocation, InlineQueryResultMpeg4Gif, InlineQueryResultPhoto, InlineQueryResultVenue, InlineQueryResultVideo, InlineQueryResultVoice, InputMessageContent, InputMessageContentLocation, - InputMessageContentText, Seconds, + InputMessageContentText, LinkPreviewOptions, Seconds, }; use mime::Mime; @@ -303,13 +303,19 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), })), caption_entities: None, }); - let expected_json = r#"{"type":"audio","id":"id","audio_file_id":"audio_file_id","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"audio","id":"id","audio_file_id":"audio_file_id","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -351,15 +357,21 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), })), caption_entities: None, performer: Some(String::from("performer")), audio_duration: Some(Seconds::from_seconds(1)), }); - let expected_json = r#"{"type":"audio","id":"id","audio_url":"http://audio_url/","title":"title","caption":"caption","parse_mode":"HTML","performer":"performer","audio_duration":1,"reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"audio","id":"id","audio_url":"http://audio_url/","title":"title","caption":"caption","parse_mode":"HTML","performer":"performer","audio_duration":1,"reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -400,13 +412,19 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), })), caption_entities: None, }); - let expected_json = r#"{"type":"document","id":"id","title":"title","document_file_id":"document_file_id","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"document","id":"id","title":"title","document_file_id":"document_file_id","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -453,15 +471,21 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), })), thumbnail_url: Some(reqwest::Url::parse("http://thumb_url/").unwrap()), thumbnail_width: Some(1), thumbnail_height: Some(1), }); - let expected_json = r#"{"type":"document","id":"id","title":"title","caption":"caption","parse_mode":"HTML","document_url":"http://document_url/","mime_type":"application/pdf","description":"description","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; + let expected_json = r#"{"type":"document","id":"id","title":"title","caption":"caption","parse_mode":"HTML","document_url":"http://document_url/","mime_type":"application/pdf","description":"description","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -501,12 +525,18 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), })), }); - let expected_json = r#"{"type":"gif","id":"id","gif_file_id":"gif_file_id","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"gif","id":"id","gif_file_id":"gif_file_id","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -556,12 +586,18 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), })), }); - let expected_json = r#"{"type":"gif","id":"id","gif_url":"http://gif_url/","gif_width":1,"gif_height":1,"gif_duration":1,"thumbnail_url":"http://thumb_url/","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"gif","id":"id","gif_url":"http://gif_url/","gif_width":1,"gif_height":1,"gif_duration":1,"thumbnail_url":"http://thumb_url/","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -601,12 +637,18 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), })), }); - let expected_json = r#"{"type":"mpeg4_gif","id":"id","mpeg4_file_id":"mpeg4_file_id","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"mpeg4_gif","id":"id","mpeg4_file_id":"mpeg4_file_id","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -656,12 +698,18 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), })), }); - let expected_json = r#"{"type":"mpeg4_gif","id":"id","mpeg4_url":"http://mpeg4_url/","mpeg4_width":1,"mpeg4_height":1,"mpeg4_duration":1,"thumbnail_url":"http://thumb_url/","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"mpeg4_gif","id":"id","mpeg4_url":"http://mpeg4_url/","mpeg4_width":1,"mpeg4_height":1,"mpeg4_duration":1,"thumbnail_url":"http://thumb_url/","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -703,12 +751,18 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), })), }); - let expected_json = r#"{"type":"photo","id":"id","photo_file_id":"photo_file_id","title":"title","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"photo","id":"id","photo_file_id":"photo_file_id","title":"title","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -756,12 +810,18 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), })), }); - let expected_json = r#"{"type":"photo","id":"id","photo_url":"http://photo_url/","thumbnail_url":"http://thumb_url/","photo_width":1,"photo_height":1,"title":"title","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"photo","id":"id","photo_url":"http://photo_url/","thumbnail_url":"http://thumb_url/","photo_width":1,"photo_height":1,"title":"title","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -794,11 +854,17 @@ mod tests { message_text: String::from("message_text"), entities: None, parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), })), }); - let expected_json = r#"{"type":"sticker","id":"id","sticker_file_id":"sticker_file_id","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"sticker","id":"id","sticker_file_id":"sticker_file_id","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -1028,8 +1094,14 @@ mod tests { input_message_content: InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), parse_mode: None, - disable_web_page_preview: None, }), reply_markup: None, url: None, @@ -1040,7 +1112,7 @@ mod tests { thumbnail_height: None, }); - let expected_json = r#"{"type":"article","id":"id","title":"title","input_message_content":{"message_text":"message_text"}}"#; + let expected_json = r#"{"type":"article","id":"id","title":"title","input_message_content":{"message_text":"message_text","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -1056,7 +1128,13 @@ mod tests { message_text: String::from("message_text"), entities: None, parse_mode: None, - disable_web_page_preview: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), }), reply_markup: Some(InlineKeyboardMarkup::default()), url: Some(Url::parse("http://url/").unwrap()), @@ -1067,7 +1145,7 @@ mod tests { thumbnail_height: Some(1), }); - let expected_json = r#"{"type":"article","id":"id","title":"title","input_message_content":{"message_text":"message_text"},"reply_markup":{"inline_keyboard":[]},"url":"http://url/","hide_url":true,"description":"description","thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; + let expected_json = r#"{"type":"article","id":"id","title":"title","input_message_content":{"message_text":"message_text","link_preview_options":{"is_disabled":true}},"reply_markup":{"inline_keyboard":[]},"url":"http://url/","hide_url":true,"description":"description","thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -1109,14 +1187,20 @@ mod tests { message_text: String::from("message_text"), entities: None, parse_mode: None, - disable_web_page_preview: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), })), thumbnail_url: Some(Url::parse("http://thumb_url/").unwrap()), thumbnail_width: Some(1), thumbnail_height: Some(1), }); - let expected_json = r#"{"type":"contact","id":"id","phone_number":"phone_number","first_name":"first_name","last_name":"last_name","vcard":"vcard","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text"},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; + let expected_json = r#"{"type":"contact","id":"id","phone_number":"phone_number","first_name":"first_name","last_name":"last_name","vcard":"vcard","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","link_preview_options":{"is_disabled":true}},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -1195,14 +1279,20 @@ mod tests { message_text: String::from("message_text"), entities: None, parse_mode: None, - disable_web_page_preview: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), })), thumbnail_url: Some(Url::parse("http://thumb_url/").unwrap()), thumbnail_width: Some(1), thumbnail_height: Some(1), }); - let expected_json = r#"{"type":"location","id":"id","latitude":1.0,"longitude":1.0,"title":"title","horizontal_accuracy":1.0,"live_period":1,"heading":1,"proximity_alert_radius":1,"reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text"},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; + let expected_json = r#"{"type":"location","id":"id","latitude":1.0,"longitude":1.0,"title":"title","horizontal_accuracy":1.0,"live_period":1,"heading":1,"proximity_alert_radius":1,"reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","link_preview_options":{"is_disabled":true}},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -1252,14 +1342,20 @@ mod tests { message_text: String::from("message_text"), entities: None, parse_mode: None, - disable_web_page_preview: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), })), thumbnail_url: Some(Url::parse("http://thumb_url/").unwrap()), thumbnail_width: Some(1), thumbnail_height: Some(1), }); - let expected_json = r#"{"type":"venue","id":"id","latitude":1.0,"longitude":1.0,"title":"title","address":"address","foursquare_id":"foursquare_id","foursquare_type":"foursquare_type","google_place_id":"google_place_id","google_place_type":"google_place_type","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text"},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; + let expected_json = r#"{"type":"venue","id":"id","latitude":1.0,"longitude":1.0,"title":"title","address":"address","foursquare_id":"foursquare_id","foursquare_type":"foursquare_type","google_place_id":"google_place_id","google_place_type":"google_place_type","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","link_preview_options":{"is_disabled":true}},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); diff --git a/crates/teloxide-core/src/types/input_media.rs b/crates/teloxide-core/src/types/input_media.rs index 535d26b6..621678f4 100644 --- a/crates/teloxide-core/src/types/input_media.rs +++ b/crates/teloxide-core/src/types/input_media.rs @@ -43,7 +43,7 @@ pub struct InputMediaPhoto { pub caption_entities: Option>, /// Pass `true` if the photo needs to be covered with a spoiler animation. - #[serde(skip_serializing_if = "std::ops::Not::not")] + #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub has_spoiler: bool, } @@ -131,7 +131,7 @@ pub struct InputMediaVideo { pub supports_streaming: Option, /// Pass `true` if the video needs to be covered with a spoiler animation. - #[serde(skip_serializing_if = "std::ops::Not::not")] + #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub has_spoiler: bool, } @@ -254,7 +254,7 @@ pub struct InputMediaAnimation { /// Pass `true` if the animation needs to be covered with a spoiler /// animation. - #[serde(skip_serializing_if = "std::ops::Not::not")] + #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub has_spoiler: bool, } diff --git a/crates/teloxide-core/src/types/input_message_content.rs b/crates/teloxide-core/src/types/input_message_content.rs index adeca069..989eb5d2 100644 --- a/crates/teloxide-core/src/types/input_message_content.rs +++ b/crates/teloxide-core/src/types/input_message_content.rs @@ -1,7 +1,7 @@ use reqwest::Url; use serde::{Deserialize, Serialize}; -use crate::types::{Currency, LabeledPrice, MessageEntity, ParseMode}; +use crate::types::{LabeledPrice, LinkPreviewOptions, MessageEntity, ParseMode}; /// This object represents the content of a message to be sent as a result of an /// inline query. @@ -36,8 +36,8 @@ pub struct InputMessageContentText { /// specified instead of `parse_mode`. pub entities: Option>, - /// Disables link previews for links in the sent message. - pub disable_web_page_preview: Option, + /// Link preview generation options for the message + pub link_preview_options: Option, } impl InputMessageContentText { @@ -48,8 +48,8 @@ impl InputMessageContentText { Self { message_text: message_text.into(), parse_mode: None, - disable_web_page_preview: None, entities: None, + link_preview_options: None, } } @@ -76,8 +76,8 @@ impl InputMessageContentText { } #[must_use] - pub fn disable_web_page_preview(mut self, val: bool) -> Self { - self.disable_web_page_preview = Some(val); + pub fn link_preview_options(mut self, val: LinkPreviewOptions) -> Self { + self.link_preview_options = Some(val); self } } @@ -329,10 +329,12 @@ pub struct InputMessageContentInvoice { /// [@Botfather]: https://t.me/Botfather pub provider_token: String, - /// Three-letter ISO 4217 currency code, see [more on currencies] + /// Three-letter ISO 4217 currency code, see [more on currencies]. Pass + /// `XTR` for payments in [Telegram Stars]. /// /// [more on currencies]: https://core.telegram.org/bots/payments#supported-currencies - pub currency: Currency, + /// [Telegram Stars]: https://t.me/BotNews/90 + pub currency: String, /// Price breakdown, list of components (e.g. product price, tax, discount, /// delivery cost, delivery tax, bonus, etc.) @@ -396,12 +398,12 @@ pub struct InputMessageContentInvoice { } impl InputMessageContentInvoice { - pub fn new( + pub fn new( title: T, description: D, payload: PA, provider_token: PT, - currency: Currency, + currency: C, prices: PR, ) -> Self where @@ -409,12 +411,14 @@ impl InputMessageContentInvoice { D: Into, PA: Into, PT: Into, + C: Into, PR: IntoIterator, { let title = title.into(); let description = description.into(); let payload = payload.into(); let provider_token = provider_token.into(); + let currency = currency.into(); let prices = prices.into_iter().collect(); Self { @@ -474,8 +478,11 @@ impl InputMessageContentInvoice { } #[must_use] - pub fn currency(mut self, val: Currency) -> Self { - self.currency = val; + pub fn currency(mut self, val: T) -> Self + where + T: Into, + { + self.currency = val.into(); self } @@ -583,12 +590,19 @@ mod tests { #[test] fn text_serialize() { - let expected_json = r#"{"message_text":"text"}"#; + let expected_json = + r#"{"message_text":"text","link_preview_options":{"is_disabled":true}}"#; let text_content = InputMessageContent::Text(InputMessageContentText { message_text: String::from("text"), parse_mode: None, - disable_web_page_preview: None, entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), }); let actual_json = serde_json::to_string(&text_content).unwrap(); diff --git a/crates/teloxide-core/src/types/input_sticker.rs b/crates/teloxide-core/src/types/input_sticker.rs index 18fffe7d..fa2677f8 100644 --- a/crates/teloxide-core/src/types/input_sticker.rs +++ b/crates/teloxide-core/src/types/input_sticker.rs @@ -2,6 +2,8 @@ use serde::Serialize; use crate::types::{InputFile, MaskPosition}; +use super::StickerFormat; + /// This object describes a sticker to be added to a sticker set. #[serde_with::skip_serializing_none] #[derive(Clone, Debug, Serialize)] @@ -16,6 +18,10 @@ pub struct InputSticker { /// More information on Sending Files pub sticker: InputFile, + /// Format of the added sticker, must be one of "static" for a .WEBP or .PNG + /// image, "animated" for a .TGS animation, "video" for a WEBM video + pub format: StickerFormat, + /// List of 1-20 emoji associated with the sticker pub emoji_list: Vec, diff --git a/crates/teloxide-core/src/types/invoice.rs b/crates/teloxide-core/src/types/invoice.rs index efeb627b..d7176cf6 100644 --- a/crates/teloxide-core/src/types/invoice.rs +++ b/crates/teloxide-core/src/types/invoice.rs @@ -16,7 +16,11 @@ pub struct Invoice { /// invoice. pub start_parameter: String, - /// Three-letter ISO 4217 currency code. + /// Three-letter ISO 4217 currency code, see [more on currencies]. Pass + /// `XTR` for payments in [Telegram Stars]. + /// + /// [more on currencies]: https://core.telegram.org/bots/payments#supported-currencies + /// [Telegram Stars]: https://t.me/BotNews/90 pub currency: String, /// Total price in the smallest units of the currency (integer, **not** diff --git a/crates/teloxide-core/src/types/keyboard_button.rs b/crates/teloxide-core/src/types/keyboard_button.rs index 2139b97e..56893d12 100644 --- a/crates/teloxide-core/src/types/keyboard_button.rs +++ b/crates/teloxide-core/src/types/keyboard_button.rs @@ -1,7 +1,7 @@ use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; use crate::types::{ - KeyboardButtonPollType, KeyboardButtonRequestChat, KeyboardButtonRequestUser, True, WebAppInfo, + KeyboardButtonPollType, KeyboardButtonRequestChat, KeyboardButtonRequestUsers, True, WebAppInfo, }; /// This object represents one button of the reply keyboard. @@ -68,10 +68,10 @@ pub enum ButtonRequest { /// [`chat_shared`]: crate::types::MessageKind::ChatShared RequestChat(KeyboardButtonRequestChat), - /// If this variant is used, pressing the button will open a list of - /// suitable users. Tapping on any user will send their identifier to the - /// bot in a โ€œuser_sharedโ€ service message. - RequestUser(KeyboardButtonRequestUser), + /// If specified, pressing the button will open a list of suitable users. + /// Identifiers of selected users will be sent to the bot in a + /// โ€œusers_sharedโ€ service message. Available in private chats only. + RequestUsers(KeyboardButtonRequestUsers), /// If this variant is used, the user will be asked to create a poll and /// send it to the bot when the button is pressed. @@ -110,10 +110,10 @@ struct RawRequest { chat: Option, /// If specified, pressing the button will open a list of suitable users. - /// Tapping on any user will send their identifier to the bot in a - /// โ€œuser_sharedโ€ service message. Available in private chats only. - #[serde(rename = "request_user")] - user: Option, + /// Identifiers of selected users will be sent to the bot in a + /// โ€œusers_sharedโ€ service message. Available in private chats only. + #[serde(rename = "request_users")] + users: Option, /// If specified, the user will be asked to create a poll and /// send it to the bot when the button is pressed. Available in private @@ -134,11 +134,11 @@ impl<'de> Deserialize<'de> for ButtonRequest { { let raw = RawRequest::deserialize(deserializer)?; match raw { - RawRequest { contact, location, chat, user, poll, web_app } + RawRequest { contact, location, chat, users, poll, web_app } if 1 < (contact.is_some() as u8 + location.is_some() as u8 + chat.is_some() as u8 - + user.is_some() as u8 + + users.is_some() as u8 + poll.is_some() as u8 + web_app.is_some() as u8) => { @@ -150,7 +150,7 @@ impl<'de> Deserialize<'de> for ButtonRequest { RawRequest { contact: Some(True), .. } => Ok(Self::Contact), RawRequest { location: Some(True), .. } => Ok(Self::Location), RawRequest { chat: Some(request_chat), .. } => Ok(Self::RequestChat(request_chat)), - RawRequest { user: Some(request_user), .. } => Ok(Self::RequestUser(request_user)), + RawRequest { users: Some(request_users), .. } => Ok(Self::RequestUsers(request_users)), RawRequest { poll: Some(poll_type), .. } => Ok(Self::Poll(poll_type)), RawRequest { web_app: Some(web_app), .. } => Ok(Self::WebApp(web_app)), @@ -158,11 +158,11 @@ impl<'de> Deserialize<'de> for ButtonRequest { contact: None, location: None, chat: None, - user: None, + users: None, poll: None, web_app: None, } => Err(D::Error::custom( - "Either one of `request_contact`, `request_chat`, `request_user`, \ + "Either one of `request_contact`, `request_chat`, `request_users`, \ `request_location`, `request_poll` and `web_app` fields is required", )), } @@ -178,7 +178,7 @@ impl Serialize for ButtonRequest { contact: None, location: None, chat: None, - user: None, + users: None, poll: None, web_app: None, }; @@ -187,7 +187,7 @@ impl Serialize for ButtonRequest { Self::Contact => raw.contact = Some(True), Self::Location => raw.location = Some(True), Self::RequestChat(request_chat) => raw.chat = Some(request_chat.clone()), - Self::RequestUser(request_user) => raw.user = Some(request_user.clone()), + Self::RequestUsers(request_users) => raw.users = Some(request_users.clone()), Self::Poll(poll_type) => raw.poll = Some(poll_type.clone()), Self::WebApp(web_app) => raw.web_app = Some(web_app.clone()), }; @@ -198,6 +198,8 @@ impl Serialize for ButtonRequest { #[cfg(test)] mod tests { + use crate::types::RequestId; + use super::*; #[test] @@ -221,7 +223,10 @@ mod tests { fn serialize_chat_request() { let button = KeyboardButton { text: String::from(""), - request: Some(ButtonRequest::RequestChat(KeyboardButtonRequestChat::new(0, false))), + request: Some(ButtonRequest::RequestChat(KeyboardButtonRequestChat::new( + RequestId(0), + false, + ))), }; let expected = r#"{"text":"","request_chat":{"request_id":0,"chat_is_channel":false}}"#; let actual = serde_json::to_string(&button).unwrap(); diff --git a/crates/teloxide-core/src/types/keyboard_button_request_chat.rs b/crates/teloxide-core/src/types/keyboard_button_request_chat.rs index fef4222b..47f9c4a3 100644 --- a/crates/teloxide-core/src/types/keyboard_button_request_chat.rs +++ b/crates/teloxide-core/src/types/keyboard_button_request_chat.rs @@ -1,10 +1,11 @@ use serde::{Deserialize, Serialize}; -use crate::types::ChatAdministratorRights; +use crate::types::{ChatAdministratorRights, RequestId}; -/// This object defines the criteria used to request a suitable chat. The -/// identifier of the selected chat will be shared with the bot when the -/// corresponding button is pressed. [More about requesting chats ยป] +/// This object defines the criteria used to request a suitable chat. +/// Information about the selected chat will be shared with the bot when the +/// corresponding button is pressed. The bot will be granted requested rights in +/// the chat if appropriate. [More about requesting chats ยป] /// /// [More about requesting chats ยป]: https://core.telegram.org/bots/features#chat-and-user-selection #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] @@ -14,7 +15,7 @@ pub struct KeyboardButtonRequestChat { /// [`ChatShared`] object. Must be unique within the message. /// /// [`ChatShared`]: crate::types::ChatShared - pub request_id: i32, + pub request_id: RequestId, /// Pass `true` to request a channel chat, pass `false` to request a group /// or a supergroup chat. @@ -53,11 +54,23 @@ pub struct KeyboardButtonRequestChat { /// additional restrictions are applied. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub bot_is_member: bool, + + /// Pass `true` to request the chat's title. + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub request_title: bool, + + /// Pass `true` to request the chat's username. + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub request_username: bool, + + /// Pass `true` to request the chat's photo. + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub request_photo: bool, } impl KeyboardButtonRequestChat { /// Creates a new [`KeyboardButtonRequestChat`]. - pub fn new(request_id: i32, chat_is_channel: bool) -> Self { + pub fn new(request_id: RequestId, chat_is_channel: bool) -> Self { Self { request_id, chat_is_channel, @@ -67,6 +80,9 @@ impl KeyboardButtonRequestChat { user_administrator_rights: None, bot_administrator_rights: None, bot_is_member: false, + request_title: false, + request_username: false, + request_photo: false, } } @@ -111,4 +127,25 @@ impl KeyboardButtonRequestChat { self.bot_is_member = value; self } + + /// Setter for `request_title` field. + #[must_use] + pub fn request_title(mut self) -> Self { + self.request_title = true; + self + } + + /// Setter for `request_username` field. + #[must_use] + pub fn request_username(mut self) -> Self { + self.request_username = true; + self + } + + /// Setter for `request_photo` field. + #[must_use] + pub fn request_photo(mut self) -> Self { + self.request_photo = true; + self + } } diff --git a/crates/teloxide-core/src/types/keyboard_button_request_user.rs b/crates/teloxide-core/src/types/keyboard_button_request_user.rs deleted file mode 100644 index 843b86d1..00000000 --- a/crates/teloxide-core/src/types/keyboard_button_request_user.rs +++ /dev/null @@ -1,44 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object defines the criteria used to request a suitable user. The -/// identifier of the selected user will be shared with the bot when the -/// corresponding button is pressed. More about requesting users ยป -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub struct KeyboardButtonRequestUser { - /// identifier of the request, which will be received back in the - /// [`UserShared`] object. Must be unique within the message. - /// - /// [`UserShared`]: crate::types::UserShared - pub request_id: i32, - - /// Pass `true` to request a bot, pass `false` to request a regular user. If - /// not specified, no additional restrictions are applied. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub user_is_bot: Option, - - /// Pass `true` to request a premium user, pass `false` to request a - /// non-premium user. If not specified, no additional restrictions are - /// applied. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub user_is_premium: Option, -} - -impl KeyboardButtonRequestUser { - /// Creates a new [`KeyboardButtonRequestUser`]. - pub fn new(request_id: i32) -> Self { - Self { request_id, user_is_bot: None, user_is_premium: None } - } - - /// Setter for `user_is_bot` field - pub fn user_is_bot(mut self, value: bool) -> Self { - self.user_is_bot = Some(value); - self - } - - /// Setter for `user_is_premium` field - pub fn user_is_premium(mut self, value: bool) -> Self { - self.user_is_premium = Some(value); - self - } -} diff --git a/crates/teloxide-core/src/types/keyboard_button_request_users.rs b/crates/teloxide-core/src/types/keyboard_button_request_users.rs new file mode 100644 index 00000000..8c38b831 --- /dev/null +++ b/crates/teloxide-core/src/types/keyboard_button_request_users.rs @@ -0,0 +1,112 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::RequestId; + +/// This object defines the criteria used to request a suitable users. +/// Information about the selected users will be shared with the bot when the +/// corresponding button is pressed. More about requesting users ยป +/// +/// [More about requesting users ยป]: https://core.telegram.org/bots/features#chat-and-user-selection +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct KeyboardButtonRequestUsers { + /// Identifier of the request, which will be received back in the + /// [`UsersShared`] object. Must be unique within the message. + /// + /// [`UsersShared`]: crate::types::UsersShared + pub request_id: RequestId, + + /// Pass `true` to request a bot, pass `false` to request a regular user. If + /// not specified, no additional restrictions are applied. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user_is_bot: Option, + + /// Pass `true` to request a premium user, pass `false` to request a + /// non-premium user. If not specified, no additional restrictions are + /// applied. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user_is_premium: Option, + + /// The maximum number of users to be selected; 1-10. Defaults to 1. + #[serde(default = "one", skip_serializing_if = "is_one")] + pub max_quantity: u8, + + /// Pass `true` to request the users' first and last names + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub request_name: bool, + + /// Pass `true` to request the users' username + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub request_username: bool, + + /// Pass `true` to request the users' photos + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub request_photo: bool, +} + +impl KeyboardButtonRequestUsers { + /// Creates a new [`KeyboardButtonRequestUsers`]. + pub fn new(request_id: RequestId) -> Self { + Self { + request_id, + user_is_bot: None, + user_is_premium: None, + max_quantity: 1, + request_name: false, + request_username: false, + request_photo: false, + } + } + + /// Setter for `user_is_bot` field + #[must_use] + pub fn user_is_bot(mut self, value: bool) -> Self { + self.user_is_bot = Some(value); + self + } + + /// Setter for `user_is_premium` field + #[must_use] + pub fn user_is_premium(mut self, value: bool) -> Self { + self.user_is_premium = Some(value); + self + } + + /// Setter for `max_quantity` field, the value must be in the range 1..=10 + #[must_use] + pub fn max_quantity(mut self, value: u8) -> Self { + assert!((1..=10).contains(&value)); + + self.max_quantity = value; + self + } + + /// Setter for `request_name` field + #[must_use] + pub fn request_name(mut self) -> Self { + self.request_name = true; + self + } + + /// Setter for `request_username` field + #[must_use] + pub fn request_username(mut self) -> Self { + self.request_username = true; + self + } + + /// Setter for `request_photo` field + #[must_use] + pub fn request_photo(mut self) -> Self { + self.request_photo = true; + self + } +} + +fn one() -> u8 { + 1 +} + +fn is_one(value: &u8) -> bool { + *value == 1 +} diff --git a/crates/teloxide-core/src/types/link_preview_options.rs b/crates/teloxide-core/src/types/link_preview_options.rs new file mode 100644 index 00000000..96407e90 --- /dev/null +++ b/crates/teloxide-core/src/types/link_preview_options.rs @@ -0,0 +1,50 @@ +use serde::{Deserialize, Serialize}; + +/// Describes the options used for link preview generation. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +pub struct LinkPreviewOptions { + /// `true`, if the link preview is disabled + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub is_disabled: bool, + + /// URL to use for the link preview. If empty, then the first URL found in + /// the message text will be used + pub url: Option, + + /// `true`, if the media in the link preview is suppposed to be shrunk; + /// ignored if the URL isn't explicitly specified or media size change isn't + /// supported for the preview + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub prefer_small_media: bool, + + /// `true`, if the media in the link preview is suppposed to be enlarged; + /// ignored if the URL isn't explicitly specified or media size change isn't + /// supported for the preview + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub prefer_large_media: bool, + + /// `true`, if the link preview must be shown above the message text; + /// otherwise, the link preview will be shown below the message text + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub show_above_text: bool, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "is_disabled": true, + "url": "https://kernel.org/", + "prefer_small_media": false, + "prefer_large_media": true, + "show_above_text": true + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/maybe_anonymous_user.rs b/crates/teloxide-core/src/types/maybe_anonymous_user.rs new file mode 100644 index 00000000..04b75a96 --- /dev/null +++ b/crates/teloxide-core/src/types/maybe_anonymous_user.rs @@ -0,0 +1,69 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{Chat, User}; + +/// Represents either [`User`] or anonymous user ([`Chat`]) that acts on behalf +/// of the chat +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum MaybeAnonymousUser { + User(User), + Chat(Chat), +} + +impl MaybeAnonymousUser { + pub fn is_user(&self) -> bool { + self.user().is_some() + } + + pub fn is_chat(&self) -> bool { + self.chat().is_some() + } + + #[must_use] + pub fn chat(&self) -> Option<&Chat> { + match self { + Self::Chat(chat) => Some(chat), + _ => None, + } + } + + #[must_use] + pub fn user(&self) -> Option<&User> { + match self { + Self::User(user) => Some(user), + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn user_de() { + let json = r#"{ + "id": 42, + "is_bot": false, + "first_name": "blah" + }"#; + + let user: MaybeAnonymousUser = serde_json::from_str(json).unwrap(); + + assert!(user.user().is_some()); + } + + #[test] + fn chat_de() { + let json = r#"{ + "id": -1001160242915, + "title": "a", + "type": "group" + }"#; + + let chat: MaybeAnonymousUser = serde_json::from_str(json).unwrap(); + + assert!(chat.chat().is_some()); + } +} diff --git a/crates/teloxide-core/src/types/maybe_inaccessible_message.rs b/crates/teloxide-core/src/types/maybe_inaccessible_message.rs new file mode 100644 index 00000000..985ea1cb --- /dev/null +++ b/crates/teloxide-core/src/types/maybe_inaccessible_message.rs @@ -0,0 +1,100 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{Chat, InaccessibleMessage, Message, MessageId}; + +/// This object describes a message that can be inaccessible to the bot. It can +/// be one of: +/// - [Message] +/// - [InaccessibleMessage] +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(untagged)] +pub enum MaybeInaccessibleMessage { + Inaccessible(InaccessibleMessage), + Regular(Message), +} + +impl MaybeInaccessibleMessage { + pub fn id(&self) -> MessageId { + match self { + Self::Inaccessible(i_message) => i_message.message_id, + Self::Regular(message) => message.id, + } + } + + #[must_use] + pub fn regular_message(&self) -> Option<&Message> { + match self { + Self::Regular(message) => Some(message), + Self::Inaccessible(_) => None, + } + } + + #[must_use] + pub fn chat(&self) -> &Chat { + match self { + Self::Regular(message) => &message.chat, + Self::Inaccessible(i_message) => &i_message.chat, + } + } +} + +impl<'de> Deserialize<'de> for MaybeInaccessibleMessage { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let message: Message = Message::deserialize(deserializer)?; + + // Thank you, TBA 7.0 authors! + if message.date.timestamp() == 0 { + return Ok(MaybeInaccessibleMessage::Inaccessible(InaccessibleMessage { + chat: message.chat, + message_id: message.id, + })); + } + Ok(MaybeInaccessibleMessage::Regular(message)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_inaccessible_message() { + let json = r#"{ + "chat": { + "id": 42, + "first_name": "ะ’ะฐะดะธะผ ะ˜ะณะพั€ะตะฒะธั‡", + "last_name": "ะกั‹ั€ั†ะตะฒ", + "username": "syrtcevvi", + "type": "private" + }, + "message_id": 4, + "date": 0 + }"#; + + let inaccessible_message = serde_json::from_str::(json); + assert!(inaccessible_message.is_ok()); + assert!(matches!(inaccessible_message.unwrap(), MaybeInaccessibleMessage::Inaccessible(_))); + } + + #[test] + fn test_regular_message() { + let json = r#"{ + "chat": { + "id": 42, + "first_name": "ะ’ะฐะดะธะผ ะ˜ะณะพั€ะตะฒะธั‡", + "last_name": "ะกั‹ั€ั†ะตะฒ", + "username": "syrtcevvi", + "type": "private" + }, + "message_id": 4, + "date": 1 + }"#; + + let regular_message = serde_json::from_str::(json); + assert!(regular_message.is_ok()); + assert!(matches!(regular_message.unwrap(), MaybeInaccessibleMessage::Regular(_))); + } +} diff --git a/crates/teloxide-core/src/types/me.rs b/crates/teloxide-core/src/types/me.rs index d5e58a9d..f404d438 100644 --- a/crates/teloxide-core/src/types/me.rs +++ b/crates/teloxide-core/src/types/me.rs @@ -23,6 +23,11 @@ pub struct Me { /// `true`, if the bot supports inline queries. pub supports_inline_queries: bool, + + /// `true`, if the bot can be connected to a Telegram Business account to + /// receive its messages. + #[serde(default)] + pub can_connect_to_business: bool, } impl Me { @@ -73,6 +78,7 @@ mod tests { can_join_groups: false, can_read_all_group_messages: false, supports_inline_queries: false, + can_connect_to_business: false, }; assert_eq!(me.username(), "SomethingSomethingBot"); diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index 2990b1aa..2b2ce6e2 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -5,13 +5,15 @@ use serde::{Deserialize, Serialize}; use url::Url; use crate::types::{ - Animation, Audio, BareChatId, Chat, ChatId, ChatShared, Contact, Dice, Document, - ForumTopicClosed, ForumTopicCreated, ForumTopicEdited, ForumTopicReopened, Game, - GeneralForumTopicHidden, GeneralForumTopicUnhidden, InlineKeyboardMarkup, Invoice, Location, - MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, MessageId, PassportData, - PhotoSize, Poll, ProximityAlertTriggered, Sticker, Story, SuccessfulPayment, ThreadId, True, - User, UserShared, Venue, Video, VideoChatEnded, VideoChatParticipantsInvited, - VideoChatScheduled, VideoChatStarted, VideoNote, Voice, WebAppData, WriteAccessAllowed, + Animation, Audio, BareChatId, BusinessConnectionId, Chat, ChatBoostAdded, ChatId, ChatShared, + Contact, Dice, Document, ExternalReplyInfo, ForumTopicClosed, ForumTopicCreated, + ForumTopicEdited, ForumTopicReopened, Game, GeneralForumTopicHidden, GeneralForumTopicUnhidden, + Giveaway, GiveawayCompleted, GiveawayCreated, GiveawayWinners, InlineKeyboardMarkup, Invoice, + LinkPreviewOptions, Location, MaybeInaccessibleMessage, MessageAutoDeleteTimerChanged, + MessageEntity, MessageEntityRef, MessageId, MessageOrigin, PassportData, PhotoSize, Poll, + ProximityAlertTriggered, Sticker, Story, SuccessfulPayment, TextQuote, ThreadId, True, User, + UsersShared, Venue, Video, VideoChatEnded, VideoChatParticipantsInvited, VideoChatScheduled, + VideoChatStarted, VideoNote, Voice, WebAppData, WriteAccessAllowed, }; /// This object represents a message. @@ -29,6 +31,15 @@ pub struct Message { #[serde(rename = "message_thread_id")] pub thread_id: Option, + /// Sender, empty for messages sent to channels. + pub from: Option, + + /// Sender of the message, sent on behalf of a chat. The channel itself for + /// channel messages. The supergroup itself for messages from anonymous + /// group administrators. The linked channel for messages automatically + /// forwarded to the discussion group + pub sender_chat: Option, + /// Date the message was sent in Unix time. #[serde(with = "crate::types::serde_date_from_unix_timestamp")] pub date: DateTime, @@ -36,9 +47,18 @@ pub struct Message { /// Conversation the message belongs to. pub chat: Chat, + /// `true`, if the message is sent to a forum topic. + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub is_topic_message: bool, + /// Bot through which the message was sent. pub via_bot: Option, + /// The bot that actually sent the message on behalf of the business + /// account. Available only for outgoing messages sent on behalf of the + /// connected business account. + pub sender_business_bot: Option, + #[serde(flatten)] pub kind: MessageKind, } @@ -61,7 +81,7 @@ pub enum MessageKind { MessageAutoDeleteTimerChanged(MessageMessageAutoDeleteTimerChanged), Pinned(MessagePinned), ChatShared(MessageChatShared), - UserShared(MessageUserShared), + UsersShared(MessageUsersShared), Invoice(MessageInvoice), SuccessfulPayment(MessageSuccessfulPayment), ConnectedWebsite(MessageConnectedWebsite), @@ -69,12 +89,17 @@ pub enum MessageKind { PassportData(MessagePassportData), Dice(MessageDice), ProximityAlertTriggered(MessageProximityAlertTriggered), + ChatBoostAdded(MessageChatBoostAdded), ForumTopicCreated(MessageForumTopicCreated), ForumTopicEdited(MessageForumTopicEdited), ForumTopicClosed(MessageForumTopicClosed), ForumTopicReopened(MessageForumTopicReopened), GeneralForumTopicHidden(MessageGeneralForumTopicHidden), GeneralForumTopicUnhidden(MessageGeneralForumTopicUnhidden), + Giveaway(MessageGiveaway), + GiveawayCompleted(MessageGiveawayCompleted), + GiveawayCreated(MessageGiveawayCreated), + GiveawayWinners(MessageGiveawayWinners), VideoChatScheduled(MessageVideoChatScheduled), VideoChatStarted(MessageVideoChatStarted), VideoChatEnded(MessageVideoChatEnded), @@ -88,28 +113,33 @@ pub enum MessageKind { #[serde_with::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageCommon { - /// Sender, empty for messages sent to channels. - pub from: Option, - - /// Sender of the message, sent on behalf of a chat. The channel itself for - /// channel messages. The supergroup itself for messages from anonymous - /// group administrators. The linked channel for messages automatically - /// forwarded to the discussion group - pub sender_chat: Option, - /// Signature of the post author for messages in channels, or the custom /// title of an anonymous group administrator. pub author_signature: Option, - /// For forwarded messages, information about the forward - #[serde(flatten)] - pub forward: Option, + /// Information about the original message for forwarded messages + pub forward_origin: Option, /// For replies, the original message. Note that the Message object in this /// field will not contain further `reply_to_message` fields even if it /// itself is a reply. pub reply_to_message: Option>, + /// Information about the message that is being replied to, which may come + /// from another chat or forum topic + pub external_reply: Option, + + /// For replies that quote part of the original message, the quoted part of + /// the message + pub quote: Option, + + /// For replies to a story, the original story + pub reply_to_story: Option, + + /// If the sender of the message boosted the chat, the number of boosts + /// added by the user + pub sender_boost_count: Option, + /// Date the message was last edited in Unix time. #[serde(default, with = "crate::types::serde_opt_date_from_unix_timestamp")] pub edit_date: Option>, @@ -121,12 +151,6 @@ pub struct MessageCommon { /// represented as ordinary `url` buttons. pub reply_markup: Option, - /// `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, skip_serializing_if = "std::ops::Not::not")] - pub is_topic_message: bool, - /// `true`, if the message is a channel post that was automatically /// forwarded to the connected discussion group. #[serde(default, skip_serializing_if = "std::ops::Not::not")] @@ -135,6 +159,17 @@ pub struct MessageCommon { /// `true`, if the message can't be forwarded. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub has_protected_content: bool, + + /// `true`, if the message was sent by an implicit action, for example, as + /// an away or a greeting business message, or as a scheduled message + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub is_from_offline: bool, + + /// Unique identifier of the business connection from which the message was + /// received. If non-empty, the message belongs to a chat of the + /// corresponding business account that is independent from any potential + /// bot chat which might share the same identifier. + pub business_connection_id: Option, } #[serde_with::skip_serializing_none] @@ -246,7 +281,7 @@ pub struct MessagePinned { /// field will not contain further `reply_to_message` fields even if it /// is itself a reply. #[serde(rename = "pinned_message")] - pub pinned: Box, + pub pinned: Box, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -256,9 +291,9 @@ pub struct MessageChatShared { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct MessageUserShared { - /// A chat was shared with the bot. - pub user_shared: UserShared, +pub struct MessageUsersShared { + /// Users were shared with the bot + pub users_shared: UsersShared, } #[serde_with::skip_serializing_none] @@ -299,52 +334,6 @@ pub struct MessagePassportData { pub passport_data: PassportData, } -/// Information about forwarded message. -#[serde_with::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Forward { - /// Date the original message was sent in Unix time. - #[serde(rename = "forward_date")] - #[serde(with = "crate::types::serde_date_from_unix_timestamp")] - pub date: DateTime, - - /// The entity that sent the original message. - #[serde(flatten)] - pub from: ForwardedFrom, - - /// For messages forwarded from channels, signature of the post author if - /// present. For messages forwarded from anonymous admins, authors title, if - /// present. - #[serde(rename = "forward_signature")] - pub signature: Option, - - /// For messages forwarded from channels, identifier of the original message - /// in the channel - #[serde( - rename = "forward_from_message_id", - with = "crate::types::option_msg_id_as_int", - default, - skip_serializing_if = "Option::is_none" - )] - pub message_id: Option, -} - -/// The entity that sent the original message that later was forwarded. -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum ForwardedFrom { - /// The message was sent by a user. - #[serde(rename = "forward_from")] - User(User), - /// The message was sent by an anonymous user on behalf of a group or - /// channel. - #[serde(rename = "forward_from_chat")] - Chat(Chat), - /// The message was sent by a user who disallow adding a link to their - /// account in forwarded messages. - #[serde(rename = "forward_sender_name")] - SenderName(String), -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum MediaKind { @@ -513,6 +502,10 @@ pub struct MediaText { /// commands, etc. that appear in the text. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub entities: Vec, + + /// Options used for link preview generation for the message, if it is a + /// text message and link preview options were changed + pub link_preview_options: Option, } #[serde_with::skip_serializing_none] @@ -584,6 +577,13 @@ pub struct MessageProximityAlertTriggered { pub proximity_alert_triggered: ProximityAlertTriggered, } +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MessageChatBoostAdded { + /// Service message. User boosted the chat. + pub boost_added: ChatBoostAdded, +} + #[serde_with::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageWriteAccessAllowed { @@ -634,6 +634,46 @@ pub struct MessageGeneralForumTopicUnhidden { pub general_forum_topic_unhidden: GeneralForumTopicUnhidden, } +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MessageGiveaway { + /// Message is giveaway, information about a scheduled giveaway. [More about + /// giveaways ยป] + /// + /// [More about giveaways ยป]: https://core.telegram.org/api#giveaways-amp-gifts + pub giveaway: Giveaway, +} + +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MessageGiveawayCompleted { + /// Service message: a 'Giveaway' completed. [More about giveaways + /// ยป] + /// + /// [More about giveaways ยป]: https://core.telegram.org/api#giveaways-amp-gifts + pub giveaway_completed: GiveawayCompleted, +} + +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MessageGiveawayCreated { + /// Service message: a scheduled 'Giveaway' created. [More about giveaways + /// ยป] + /// + /// [More about giveaways ยป]: https://core.telegram.org/api#giveaways-amp-gifts + pub giveaway_created: GiveawayCreated, +} + +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MessageGiveawayWinners { + /// Message is giveaway winners, information about the completion of a + /// giveaway with public winners. [More about giveaways ยป] + /// + /// [More about giveaways ยป]: https://core.telegram.org/api#giveaways-amp-gifts + pub giveaway_winners: GiveawayWinners, +} + #[serde_with::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageVideoChatScheduled { @@ -674,22 +714,23 @@ mod getters { use std::ops::Deref; use crate::types::{ - self, message::MessageKind::*, Chat, ChatId, ChatMigration, Forward, ForwardedFrom, - MediaAnimation, MediaAudio, MediaContact, MediaDocument, MediaGame, MediaKind, - MediaLocation, MediaPhoto, MediaPoll, MediaSticker, MediaStory, MediaText, MediaVenue, - MediaVideo, MediaVideoNote, MediaVoice, Message, MessageChannelChatCreated, - MessageChatShared, MessageCommon, MessageConnectedWebsite, MessageDeleteChatPhoto, - MessageDice, MessageEntity, MessageGroupChatCreated, MessageId, MessageInvoice, - MessageLeftChatMember, MessageNewChatMembers, MessageNewChatPhoto, MessageNewChatTitle, - MessagePassportData, MessagePinned, MessageProximityAlertTriggered, - MessageSuccessfulPayment, MessageSupergroupChatCreated, MessageUserShared, - MessageVideoChatParticipantsInvited, PhotoSize, User, + self, message::MessageKind::*, Chat, ChatId, ChatMigration, LinkPreviewOptions, + MaybeInaccessibleMessage, MediaAnimation, MediaAudio, MediaContact, MediaDocument, + MediaGame, MediaKind, MediaLocation, MediaPhoto, MediaPoll, MediaSticker, MediaStory, + MediaText, MediaVenue, MediaVideo, MediaVideoNote, MediaVoice, Message, + MessageChannelChatCreated, MessageChatShared, MessageCommon, MessageConnectedWebsite, + MessageDeleteChatPhoto, MessageDice, MessageEntity, MessageGroupChatCreated, MessageId, + MessageInvoice, MessageLeftChatMember, MessageNewChatMembers, MessageNewChatPhoto, + MessageNewChatTitle, MessageOrigin, MessagePassportData, MessagePinned, + MessageProximityAlertTriggered, MessageSuccessfulPayment, MessageSupergroupChatCreated, + MessageUsersShared, MessageVideoChatParticipantsInvited, PhotoSize, Story, TextQuote, User, }; use super::{ - MessageForumTopicClosed, MessageForumTopicCreated, MessageForumTopicEdited, - MessageForumTopicReopened, MessageGeneralForumTopicHidden, - MessageGeneralForumTopicUnhidden, MessageMessageAutoDeleteTimerChanged, + MessageChatBoostAdded, MessageForumTopicClosed, MessageForumTopicCreated, + MessageForumTopicEdited, MessageForumTopicReopened, MessageGeneralForumTopicHidden, + MessageGeneralForumTopicUnhidden, MessageGiveaway, MessageGiveawayCompleted, + MessageGiveawayCreated, MessageGiveawayWinners, MessageMessageAutoDeleteTimerChanged, MessageVideoChatEnded, MessageVideoChatScheduled, MessageVideoChatStarted, MessageWebAppData, MessageWriteAccessAllowed, }; @@ -700,12 +741,10 @@ mod getters { /// [telegram docs]: https://core.telegram.org/bots/api#message impl Message { /// Returns the user who sent the message. + #[deprecated(since = "0.13.0", note = "use `.from` field instead")] #[must_use] pub fn from(&self) -> Option<&User> { - match &self.kind { - Common(MessageCommon { from, .. }) => from.as_ref(), - _ => None, - } + self.from.as_ref() } #[must_use] @@ -716,61 +755,86 @@ mod getters { } } + #[deprecated(since = "0.13.0", note = "use `.sender_chat` field instead")] #[must_use] pub fn sender_chat(&self) -> Option<&Chat> { + self.sender_chat.as_ref() + } + + #[must_use] + pub fn forward_origin(&self) -> Option<&MessageOrigin> { match &self.kind { - Common(MessageCommon { sender_chat, .. }) => sender_chat.as_ref(), + Common(MessageCommon { forward_origin, .. }) => forward_origin.as_ref(), _ => None, } } #[must_use] - pub fn forward(&self) -> Option<&Forward> { - self.common().and_then(|m| m.forward.as_ref()) + pub fn quote(&self) -> Option<&TextQuote> { + match &self.kind { + Common(MessageCommon { quote, .. }) => quote.as_ref(), + _ => None, + } + } + + #[must_use] + pub fn reply_to_story(&self) -> Option<&Story> { + match &self.kind { + Common(MessageCommon { reply_to_story, .. }) => reply_to_story.as_ref(), + _ => None, + } } #[must_use] pub fn forward_date(&self) -> Option> { - self.forward().map(|f| f.date) - } - - #[must_use] - pub fn forward_from(&self) -> Option<&ForwardedFrom> { - self.forward().map(|f| &f.from) + self.forward_origin().map(|f| f.date()) } #[must_use] pub fn forward_from_user(&self) -> Option<&User> { - self.forward_from().and_then(|from| match from { - ForwardedFrom::User(user) => Some(user), + self.forward_origin().and_then(|origin| match origin { + MessageOrigin::User { sender_user, .. } => Some(sender_user), _ => None, }) } #[must_use] pub fn forward_from_chat(&self) -> Option<&Chat> { - self.forward_from().and_then(|from| match from { - ForwardedFrom::Chat(chat) => Some(chat), + self.forward_origin().and_then(|origin| match origin { + MessageOrigin::Chat { sender_chat, .. } => Some(sender_chat), _ => None, }) } #[must_use] pub fn forward_from_sender_name(&self) -> Option<&str> { - self.forward_from().and_then(|from| match from { - ForwardedFrom::SenderName(sender_name) => Some(&**sender_name), + self.forward_origin().and_then(|origin| match origin { + MessageOrigin::HiddenUser { sender_user_name, .. } => { + Some(sender_user_name.as_str()) + } _ => None, }) } #[must_use] pub fn forward_from_message_id(&self) -> Option { - self.forward().and_then(|f| f.message_id) + self.forward_origin().and_then(|origin| match origin { + MessageOrigin::Channel { message_id, .. } => Some(*message_id), + _ => None, + }) } #[must_use] - pub fn forward_signature(&self) -> Option<&str> { - self.forward().and_then(|f| f.signature.as_deref()) + pub fn forward_author_signature(&self) -> Option<&str> { + self.forward_origin().and_then(|origin| match origin { + MessageOrigin::Chat { author_signature, .. } => { + author_signature.as_ref().map(|a| a.as_str()) + } + MessageOrigin::Channel { author_signature, .. } => { + author_signature.as_ref().map(|a| a.as_str()) + } + _ => None, + }) } #[must_use] @@ -843,6 +907,17 @@ mod getters { } } + #[must_use] + pub fn link_preview_options(&self) -> Option<&LinkPreviewOptions> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Text(MediaText { link_preview_options, .. }), + .. + }) => link_preview_options.as_ref(), + _ => None, + } + } + /// Returns message entities that represent text formatting. /// /// **Note:** you probably want to use [`parse_caption_entities`] @@ -1244,7 +1319,7 @@ mod getters { } #[must_use] - pub fn pinned_message(&self) -> Option<&Message> { + pub fn pinned_message(&self) -> Option<&MaybeInaccessibleMessage> { match &self.kind { Pinned(MessagePinned { pinned }) => Some(pinned), _ => None, @@ -1306,9 +1381,9 @@ mod getters { } #[must_use] - pub fn shared_user(&self) -> Option<&types::UserShared> { + pub fn shared_users(&self) -> Option<&types::UsersShared> { match &self.kind { - UserShared(MessageUserShared { user_shared }) => Some(user_shared), + UsersShared(MessageUsersShared { users_shared }) => Some(users_shared), _ => None, } } @@ -1331,6 +1406,14 @@ mod getters { } } + #[must_use] + pub fn boost_added(&self) -> Option<&types::ChatBoostAdded> { + match &self.kind { + ChatBoostAdded(MessageChatBoostAdded { boost_added }) => Some(boost_added), + _ => None, + } + } + #[must_use] pub fn forum_topic_created(&self) -> Option<&types::ForumTopicCreated> { match &self.kind { @@ -1391,6 +1474,44 @@ mod getters { } } + #[must_use] + pub fn giveaway(&self) -> Option<&types::Giveaway> { + match &self.kind { + Giveaway(MessageGiveaway { giveaway }) => Some(giveaway), + _ => None, + } + } + + #[must_use] + pub fn giveaway_completed(&self) -> Option<&types::GiveawayCompleted> { + match &self.kind { + GiveawayCompleted(MessageGiveawayCompleted { giveaway_completed }) => { + Some(giveaway_completed) + } + _ => None, + } + } + + #[must_use] + pub fn giveaway_created(&self) -> Option<&types::GiveawayCreated> { + match &self.kind { + GiveawayCreated(MessageGiveawayCreated { giveaway_created }) => { + Some(giveaway_created) + } + _ => None, + } + } + + #[must_use] + pub fn giveaway_winners(&self) -> Option<&types::GiveawayWinners> { + match &self.kind { + GiveawayWinners(MessageGiveawayWinners { giveaway_winners }) => { + Some(giveaway_winners) + } + _ => None, + } + } + #[must_use] pub fn video_chat_scheduled(&self) -> Option<&types::VideoChatScheduled> { match &self.kind { @@ -1670,8 +1791,8 @@ impl Message { // Lets just hope we didn't forget something here... - self.from() - .into_iter() + self.from + .iter() .chain(self.via_bot.as_ref()) .chain(self.chat.mentioned_users_rec()) .chain(flatten(self.reply_to_message().map(Self::mentioned_users_rec))) @@ -1697,6 +1818,7 @@ impl Message { #[cfg(test)] mod tests { + use chrono::DateTime; use cool_asserts::assert_matches; use serde_json::from_str; @@ -1767,7 +1889,10 @@ mod tests { Message { id: MessageId(198283), thread_id: None, + from: None, + sender_chat: None, date: chrono::DateTime::from_timestamp(1567927221, 0).unwrap(), + is_topic_message: false, chat: Chat { id: ChatId(250918540), kind: ChatKind::Private(ChatPrivate { @@ -1775,19 +1900,31 @@ mod tests { last_name: Some("ะ’ะปะฐัะพะฒ".to_string()), username: Some("aka_dude".to_string()), bio: None, - emoji_status_custom_emoji_id: None, has_private_forwards: None, - has_restricted_voice_and_video_messages: None + has_restricted_voice_and_video_messages: None, + personal_chat: None, + birthdate: None, + business_intro: None, + business_location: None, + business_opening_hours: None, }), photo: None, + available_reactions: None, has_aggressive_anti_spam_enabled: false, pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None } + chat_full_info: ChatFullInfo::default() }, + sender_business_bot: None, kind: MessageKind::ChatShared(MessageChatShared { - chat_shared: ChatShared { request_id: 348349, chat_id: ChatId(384939) } + chat_shared: ChatShared { + request_id: RequestId(348349), + chat_id: ChatId(384939), + title: None, + username: None, + photo: None, + } }), via_bot: None } @@ -1975,14 +2112,17 @@ mod tests { "title": "a", "type": "supergroup" }, - "date": 1640359576, - "forward_from_chat": { - "id": -1001160242915, - "title": "a", - "type": "supergroup" + "forward_origin": { + "type": "chat", + "date": 1640359544, + "sender_chat": { + "id": -1001160242915, + "title": "a", + "type": "supergroup" + }, + "author_signature": "TITLE" }, - "forward_signature": "TITLE", - "forward_date": 1640359544, + "date": 1640359576, "text": "text" }"#; @@ -1993,14 +2133,16 @@ mod tests { let group = Chat { id: ChatId(-1001160242915), - kind: ChatKind::Public(ChatPublic { + kind: ChatKind::Public(Box::new(ChatPublic { title: Some("a".to_owned()), kind: PublicChatKind::Supergroup(PublicChatSupergroup { username: None, sticker_set_name: None, can_set_sticker_set: None, + custom_emoji_sticker_set_name: None, permissions: None, slow_mode_delay: None, + unrestrict_boost_count: None, linked_chat_id: None, location: None, join_by_request: None, @@ -2011,21 +2153,22 @@ mod tests { description: None, invite_link: None, has_protected_content: None, - }), + })), message_auto_delete_time: None, photo: None, + available_reactions: None, pinned_message: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None }, + chat_full_info: ChatFullInfo::default(), }; - assert!(message.from().unwrap().is_anonymous()); + assert!(message.from.as_ref().unwrap().is_anonymous()); assert_eq!(message.author_signature().unwrap(), "TITLE2"); - assert_eq!(message.sender_chat().unwrap(), &group); + assert_eq!(message.sender_chat.as_ref().unwrap(), &group); assert_eq!(&message.chat, &group); assert_eq!(message.forward_from_chat().unwrap(), &group); - assert_eq!(message.forward_signature().unwrap(), "TITLE"); + assert_eq!(message.forward_author_signature().unwrap(), "TITLE"); assert!(message.forward_date().is_some()); assert_eq!(message.text().unwrap(), "text"); } @@ -2045,7 +2188,7 @@ mod tests { assert_eq!(message.migrate_to_chat_id(), Some(&new)); // The user who initialized the migration - assert!(message.from().is_some()); + assert!(message.from.is_some()); // Migration from a common group let json = r#"{"chat":{"id":-1001555296434,"title":"test","type":"supergroup"},"date":1629404938,"from":{"first_name":"Group","id":1087968824,"is_bot":true,"username":"GroupAnonymousBot"},"message_id":1,"migrate_from_chat_id":-599075523,"sender_chat":{"id":-1001555296434,"title":"test","type":"supergroup"}}"#; @@ -2056,10 +2199,10 @@ mod tests { assert_eq!(message.migrate_from_chat_id(), Some(&old)); // Anonymous bot - assert!(message.from().is_some()); + assert!(message.from.is_some()); // The chat to which the group migrated - assert!(message.sender_chat().is_some()); + assert!(message.sender_chat.is_some()); } /// Regression test for @@ -2215,7 +2358,9 @@ mod tests { "message_thread_id":4 }"#; - let _: Message = serde_json::from_str(json).unwrap(); + let message: Message = serde_json::from_str(json).unwrap(); + // https://github.com/teloxide/teloxide/issues/945 + assert!(message.from.is_some()); } #[test] @@ -2257,4 +2402,362 @@ mod tests { let _: Message = serde_json::from_str(json).unwrap(); } + + #[test] + fn giveaway() { + let json = r#"{ + "message_id": 27, + "sender_chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "date": 1721162577, + "giveaway": { + "chats": [ + { + "id": -1002236736395, + "title": "Test", + "type": "channel" + } + ], + "winners_selection_date": 1721162701, + "winner_count": 1, + "has_public_winners": true, + "premium_subscription_month_count": 6 + } + }"#; + let message: Message = from_str(json).unwrap(); + assert_eq!( + message.giveaway().unwrap(), + &Giveaway { + chats: vec![Chat { + id: ChatId(-1002236736395), + kind: ChatKind::Public(Box::new(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Channel(PublicChatChannel { + username: None, + linked_chat_id: None + }), + description: None, + invite_link: None, + has_protected_content: None + })), + photo: None, + available_reactions: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo::default() + }], + winners_selection_date: DateTime::from_timestamp(1721162701, 0).unwrap(), + winner_count: 1, + only_new_members: false, + has_public_winners: true, + prize_description: None, + country_codes: None, + premium_subscription_month_count: Some(6) + } + ) + } + + #[test] + fn giveaway_created() { + let json = r#"{ + "message_id": 27, + "sender_chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "date": 1721162577, + "giveaway_created": {} + }"#; + let message: Message = from_str(json).unwrap(); + assert_eq!(message.giveaway_created().unwrap(), &GiveawayCreated {}) + } + + #[test] + fn giveaway_completed() { + let json = r#"{ + "message_id": 27, + "sender_chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "date": 1721162577, + "giveaway_completed": { + "winner_count": 0, + "unclaimed_prize_count": 1, + "giveaway_message": { + "message_id": 24, + "sender_chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "date": 1721161230, + "giveaway": { + "chats": [ + { + "id": -1002236736395, + "title": "Test", + "type": "channel" + } + ], + "winners_selection_date": 1721162701, + "winner_count": 1, + "has_public_winners": true, + "premium_subscription_month_count": 6 + } + } + } + }"#; + let message: Message = from_str(json).unwrap(); + assert_eq!( + message.giveaway_completed().unwrap(), + &GiveawayCompleted { + winner_count: 0, + unclaimed_prize_count: Some(1), + giveaway_message: Some(Box::new(Message { + id: MessageId(24), + thread_id: None, + from: None, + sender_chat: Some(Chat { + id: ChatId(-1002236736395), + kind: ChatKind::Public(Box::new(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Channel(PublicChatChannel { + linked_chat_id: None, + username: None + }), + description: None, + invite_link: None, + has_protected_content: None + })), + chat_full_info: ChatFullInfo::default(), + available_reactions: None, + photo: None, + has_aggressive_anti_spam_enabled: false, + has_hidden_members: false, + message_auto_delete_time: None, + pinned_message: None + }), + is_topic_message: false, + date: DateTime::from_timestamp(1721161230, 0).unwrap(), + chat: Chat { + id: ChatId(-1002236736395), + kind: ChatKind::Public(Box::new(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Channel(PublicChatChannel { + username: None, + linked_chat_id: None + }), + description: None, + invite_link: None, + has_protected_content: None + })), + photo: None, + available_reactions: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo::default() + }, + via_bot: None, + sender_business_bot: None, + kind: MessageKind::Giveaway(MessageGiveaway { + giveaway: Giveaway { + chats: vec![Chat { + id: ChatId(-1002236736395), + kind: ChatKind::Public(Box::new(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Channel(PublicChatChannel { + username: None, + linked_chat_id: None + }), + description: None, + invite_link: None, + has_protected_content: None + })), + photo: None, + available_reactions: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo::default() + }], + winners_selection_date: DateTime::from_timestamp(1721162701, 0) + .unwrap(), + winner_count: 1, + only_new_members: false, + has_public_winners: true, + prize_description: None, + country_codes: None, + premium_subscription_month_count: Some(6) + } + }) + })) + } + ) + } + + #[test] + fn giveaway_winners() { + let json = r#"{ + "message_id": 28, + "sender_chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "date": 1721162702, + "reply_to_message": { + "message_id": 27, + "sender_chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "date": 1721162577, + "giveaway": { + "chats": [ + { + "id": -1002236736395, + "title": "Test", + "type": "channel" + } + ], + "winners_selection_date": 1721162701, + "winner_count": 1, + "has_public_winners": true, + "premium_subscription_month_count": 6 + } + }, + "giveaway_winners": { + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "giveaway_message_id": 27, + "winners_selection_date": 1721162701, + "premium_subscription_month_count": 6, + "winner_count": 1, + "winners": [ + { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10" + } + ] + } + }"#; + let message: Message = from_str(json).unwrap(); + assert_eq!( + message.giveaway_winners().expect("Failed to get GiveawayWinners from Message!"), + &GiveawayWinners { + chat: Chat { + id: ChatId(-1002236736395), + kind: ChatKind::Public(Box::new(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Channel(PublicChatChannel { + username: None, + linked_chat_id: None + }), + description: None, + invite_link: None, + has_protected_content: None + })), + photo: None, + available_reactions: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo::default() + }, + giveaway_message_id: MessageId(27), + winners_selection_date: DateTime::from_timestamp(1721162701, 0).unwrap(), + winner_count: 1, + winners: vec![User { + id: UserId(1459074222), + is_bot: false, + first_name: "shadowchain".to_owned(), + last_name: None, + username: Some("shdwchn10".to_owned()), + language_code: None, + is_premium: false, + added_to_attachment_menu: false + }], + additional_chat_count: None, + premium_subscription_month_count: Some(6), + unclaimed_prize_count: None, + only_new_members: false, + was_refunded: false, + prize_description: None + } + ) + } + + #[test] + fn chat_boost_added() { + let json = r#"{ + "message_id": 28, + "sender_chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "date": 1721162702, + "boost_added": { + "boost_count": 4 + } + }"#; + let message: Message = from_str(json).unwrap(); + assert_eq!( + message.boost_added().expect("Failed to get ChatBoostAdded from Message!"), + &ChatBoostAdded { boost_count: 4 } + ) + } } diff --git a/crates/teloxide-core/src/types/message_entity.rs b/crates/teloxide-core/src/types/message_entity.rs index c6cb7c8e..df4c4d0d 100644 --- a/crates/teloxide-core/src/types/message_entity.rs +++ b/crates/teloxide-core/src/types/message_entity.rs @@ -249,6 +249,7 @@ pub enum MessageEntityKind { Email, PhoneNumber, Bold, + Blockquote, Italic, Underline, Strikethrough, @@ -285,6 +286,18 @@ mod tests { ); } + // https://github.com/teloxide/teloxide/issues/1062 + #[test] + fn blockquote() { + use serde_json::from_str; + + assert_eq!( + MessageEntity { kind: MessageEntityKind::Blockquote, offset: 32, length: 92 }, + from_str::(r#"{"type": "blockquote", "offset": 32, "length": 92}"#) + .unwrap() + ); + } + #[test] fn pre() { use serde_json::from_str; diff --git a/crates/teloxide-core/src/types/message_id.rs b/crates/teloxide-core/src/types/message_id.rs index d0f1212b..186f431d 100644 --- a/crates/teloxide-core/src/types/message_id.rs +++ b/crates/teloxide-core/src/types/message_id.rs @@ -1,7 +1,18 @@ use serde::{Deserialize, Serialize}; /// A unique message identifier. -#[derive(Clone, Copy, Debug, derive_more::Display, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive( + Default, + Clone, + Copy, + Debug, + derive_more::Display, + PartialEq, + Eq, + Hash, + Serialize, + Deserialize +)] #[serde(from = "MessageIdRaw", into = "MessageIdRaw")] pub struct MessageId(pub i32); @@ -11,6 +22,9 @@ pub struct MessageId(pub i32); // // (we can't change the default format of `MessageId` because it's returned // by some methods and we can't change serialization there) +// If you flatten `MessageId` within serde-multipart request, it will fail, see https://github.com/teloxide/teloxide/issues/1135 +// Try to use `serde(with = "crate::types::msg_id_as_int")]` instead of +// `#[serde(flatten)]` #[derive(Serialize, Deserialize)] struct MessageIdRaw { diff --git a/crates/teloxide-core/src/types/message_origin.rs b/crates/teloxide-core/src/types/message_origin.rs new file mode 100644 index 00000000..1c1e4f0d --- /dev/null +++ b/crates/teloxide-core/src/types/message_origin.rs @@ -0,0 +1,58 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::{Chat, MessageId, User}; + +/// This object describes the origin of a message +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(tag = "type")] +pub enum MessageOrigin { + User { + /// Date the message was sent originally in Unix time + #[serde(default, with = "crate::types::serde_date_from_unix_timestamp")] + date: DateTime, + /// User that sent the message originally + sender_user: User, + }, + HiddenUser { + /// Date the message was sent originally in Unix time + #[serde(default, with = "crate::types::serde_date_from_unix_timestamp")] + date: DateTime, + /// Name of the user that sent the message originally + sender_user_name: String, + }, + Chat { + /// Date the message was sent originally in Unix time + #[serde(default, with = "crate::types::serde_date_from_unix_timestamp")] + date: DateTime, + /// Chat that sent the message originally + sender_chat: Chat, + /// For messages originally sent by an anonymous chat administrator, + /// original message author signature + author_signature: Option, + }, + Channel { + /// Date the message was sent originally in Unix time + #[serde(default, with = "crate::types::serde_date_from_unix_timestamp")] + date: DateTime, + /// Channel chat to which the message was originally sent + chat: Chat, + /// Unique message identifier inside the chat + #[serde(flatten)] + message_id: MessageId, + /// Signature of the original post author + author_signature: Option, + }, +} + +impl MessageOrigin { + pub fn date(&self) -> DateTime { + *match self { + Self::User { date, .. } => date, + Self::HiddenUser { date, .. } => date, + Self::Chat { date, .. } => date, + Self::Channel { date, .. } => date, + } + } +} diff --git a/crates/teloxide-core/src/types/message_reaction_count_updated.rs b/crates/teloxide-core/src/types/message_reaction_count_updated.rs new file mode 100644 index 00000000..0d9f1a0e --- /dev/null +++ b/crates/teloxide-core/src/types/message_reaction_count_updated.rs @@ -0,0 +1,73 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::{Chat, MessageId, ReactionType}; + +/// This object represents reaction changes on a message with anonymous +/// reactions. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MessageReactionCountUpdated { + /// The chat containing the message + pub chat: Chat, + + /// Unique message identifier inside the chat + #[serde(flatten)] + pub message_id: MessageId, + + /// Date of the change in Unix time + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub date: DateTime, + + /// List of reactions that are present on the message + pub reactions: Vec, +} + +/// Represents a reaction added to a message along with the number of times it +/// was added. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ReactionCount { + /// Type of the reaction + pub r#type: ReactionType, + + /// Number of times the reaction was added + pub total_count: u64, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "message_id": 36, + "date": 1721306391, + "reactions": [ + { + "type": { + "type": "emoji", + "emoji": "๐Ÿ—ฟ" + }, + "total_count": 2 + }, + { + "type": { + "type": "emoji", + "emoji": "๐ŸŒญ" + }, + "total_count": 1 + } + ] + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/message_reaction_updated.rs b/crates/teloxide-core/src/types/message_reaction_updated.rs new file mode 100644 index 00000000..5ec884c3 --- /dev/null +++ b/crates/teloxide-core/src/types/message_reaction_updated.rs @@ -0,0 +1,127 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Deserializer, Serialize}; + +use crate::types::{Chat, MaybeAnonymousUser, MessageId, ReactionType, User}; + +/// This object represents a change of a reaction on a message performed by a +/// user. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MessageReactionUpdated { + /// The chat containing the message the user reacted to + pub chat: Chat, + + /// Unique identifier of the message inside the chat + #[serde(flatten)] + pub message_id: MessageId, + + /// The [`MaybeAnonymousUser::User`] that changed the reaction, if the user + /// isn't anonymous or the [`MaybeAnonymousUser::Chat`] on behalf of + /// which the reaction was changed, if the user is anonymous + #[serde(deserialize_with = "deserialize_actor", flatten)] + pub actor: MaybeAnonymousUser, + + /// Date of the change in Unix time + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub date: DateTime, + + /// Previous list of reaction types that were set by the user + pub old_reaction: Vec, + + /// New list of reaction types that have been set by the user + pub new_reaction: Vec, +} + +impl MessageReactionUpdated { + #[must_use] + pub fn chat(&self) -> Option<&Chat> { + self.actor.chat() + } + + #[must_use] + pub fn user(&self) -> Option<&User> { + self.actor.user() + } +} + +#[derive(Deserialize)] +struct ActorDe { + /// The user that changed the reaction, if the user isn't anonymous + user: Option, + /// The chat on behalf of which the reaction was changed, if the user is + /// anonymous + actor_chat: Option, +} + +fn deserialize_actor<'d, D: Deserializer<'d>>(d: D) -> Result { + let ActorDe { user, actor_chat } = ActorDe::deserialize(d)?; + + Ok(actor_chat.map(MaybeAnonymousUser::Chat).or(user.map(MaybeAnonymousUser::User)).unwrap()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize_user() { + let data = r#" + { + "chat": { + "id": -1002184233434, + "title": "Test", + "type": "supergroup" + }, + "message_id": 35, + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + }, + "date": 1721306082, + "old_reaction": [], + "new_reaction": [ + { + "type": "emoji", + "emoji": "๐ŸŒญ" + } + ] + } + "#; + let message_reaction_update = serde_json::from_str::(data).unwrap(); + + assert!(message_reaction_update.actor.is_user()); + } + + #[test] + fn deserialize_chat() { + let data = r#"{ + "chat": { + "id": -1002199793788, + "title": "ั‚ะตัั‚", + "type": "supergroup" + }, + "message_id": 2, + "actor_chat": { + "id": -1002199793788, + "title": "ั‚ะตัั‚", + "type": "supergroup" + }, + "date": 1723798597, + "old_reaction": [ + { + "type": "emoji", + "emoji": "โค" + } + ], + "new_reaction": [] + }"#; + + let message_reaction_update = serde_json::from_str::(data).unwrap(); + + assert!(message_reaction_update.actor.is_chat()) + } +} diff --git a/crates/teloxide-core/src/types/non_telegram_types/currency.rs b/crates/teloxide-core/src/types/non_telegram_types/currency.rs deleted file mode 100644 index a252b48b..00000000 --- a/crates/teloxide-core/src/types/non_telegram_types/currency.rs +++ /dev/null @@ -1,365 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// ISO 4217 currency. -#[allow(clippy::upper_case_acronyms)] -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub enum Currency { - /// United Arab Emirates dirham - AED, - /// Afghan afghani - AFN, - /// Albanian lek - ALL, - /// Armenian dram - AMD, - /// Netherlands Antillean guilder - ANG, - /// Angolan kwanza - AOA, - /// Argentine peso - ARS, - /// Australian dollar - AUD, - /// Aruban florin - AWG, - /// Azerbaijani manat - AZN, - /// Bosnia and Herzegovina convertible mark - BAM, - /// Barbados dollar - BBD, - /// Bangladeshi taka - BDT, - /// Bulgarian lev - BGN, - /// Bahraini dinar - BHD, - /// Burundian franc - BIF, - /// Bermudian dollar - BMD, - /// Brunei dollar - BND, - /// Boliviano - BOB, - /// Bolivian Mvdol (funds code) - BOV, - /// Brazilian real - BRL, - /// Bahamian dollar - BSD, - /// Bhutanese ngultrum - BTN, - /// Botswana pula - BWP, - /// Belarusian ruble - BYN, - /// Belize dollar - BZD, - /// Canadian dollar - CAD, - /// Congolese franc - CDF, - /// WIR euro (complementary currency) - CHE, - /// Swiss franc - CHF, - /// WIR franc (complementary currency) - CHW, - /// Unidad de Fomento (funds code) - CLF, - /// Chilean peso - CLP, - /// Chinese yuan - CNY, - /// Colombian peso - COP, - /// Unidad de Valor Real (UVR) (funds code) - COU, - /// Costa Rican colon - CRC, - /// Cuban convertible peso - CUC, - /// Cuban peso - CUP, - /// Cape Verdean escudo - CVE, - /// Czech koruna - CZK, - /// Djiboutian franc - DJF, - /// Danish krone - DKK, - /// Dominican peso - DOP, - /// Algerian dinar - DZD, - /// Egyptian pound - EGP, - /// Eritrean nakfa - ERN, - /// Ethiopian birr - ETB, - /// Euro - EUR, - /// Fiji dollar - FJD, - /// Falkland Islands pound - FKP, - /// Pound sterling - GBP, - /// Georgian lari - GEL, - /// Ghanaian cedi - GHS, - /// Gibraltar pound - GIP, - /// Gambian dalasi - GMD, - /// Guinean franc - GNF, - /// Guatemalan quetzal - GTQ, - /// Guyanese dollar - GYD, - /// Hong Kong dollar - HKD, - /// Honduran lempira - HNL, - /// Croatian kuna - HRK, - /// Haitian gourde - HTG, - /// Hungarian forint - HUF, - /// Indonesian rupiah - IDR, - /// Israeli new shekel - ILS, - /// Indian rupee - INR, - /// Iraqi dinar - IQD, - /// Iranian rial - IRR, - /// Icelandic krรณna - ISK, - /// Jamaican dollar - JMD, - /// Jordanian dinar - JOD, - /// Japanese yen - JPY, - /// Kenyan shilling - KES, - /// Kyrgyzstani som - KGS, - /// Cambodian riel - KHR, - /// Comoro franc - KMF, - /// North Korean won - KPW, - /// South Korean won - KRW, - /// Kuwaiti dinar - KWD, - /// Cayman Islands dollar - KYD, - /// Kazakhstani tenge - KZT, - /// Lao kip - LAK, - /// Lebanese pound - LBP, - /// Sri Lankan rupee - LKR, - /// Liberian dollar - LRD, - /// Lesotho loti - LSL, - /// Libyan dinar - LYD, - /// Moroccan dirham - MAD, - /// Moldovan leu - MDL, - /// Malagasy ariary - MGA, - /// Macedonian denar - MKD, - /// Myanmar kyat - MMK, - /// Mongolian tรถgrรถg - MNT, - /// Macanese pataca - MOP, - /// Mauritanian ouguiya - MRU, - /// Mauritian rupee - MUR, - /// Maldivian rufiyaa - MVR, - /// Malawian kwacha - MWK, - /// Mexican peso - MXN, - /// Mexican Unidad de Inversion (UDI) (funds code) - MXV, - /// Malaysian ringgit - MYR, - /// Mozambican metical - MZN, - /// Namibian dollar - NAD, - /// Nigerian naira - NGN, - /// Nicaraguan cรณrdoba - NIO, - /// Norwegian krone - NOK, - /// Nepalese rupee - NPR, - /// New Zealand dollar - NZD, - /// Omani rial - OMR, - /// Panamanian balboa - PAB, - /// Peruvian sol - PEN, - /// Papua New Guinean kina - PGK, - /// Philippine peso - PHP, - /// Pakistani rupee - PKR, - /// Polish zล‚oty - PLN, - /// Paraguayan guaranรญ - PYG, - /// Qatari riyal - QAR, - /// Romanian leu - RON, - /// Serbian dinar - RSD, - /// Russian ruble - RUB, - /// Rwandan franc - RWF, - /// Saudi riyal - SAR, - /// Solomon Islands dollar - SBD, - /// Seychelles rupee - SCR, - /// Sudanese pound - SDG, - /// Swedish krona/kronor - SEK, - /// Singapore dollar - SGD, - /// Saint Helena pound - SHP, - /// Sierra Leonean leone - SLL, - /// Somali shilling - SOS, - /// Surinamese dollar - SRD, - /// South Sudanese pound - SSP, - /// Sรฃo Tomรฉ and Prรญncipe dobra - STN, - /// Salvadoran colรณn - SVC, - /// Syrian pound - SYP, - /// Swazi lilangeni - SZL, - /// Thai baht - THB, - /// Tajikistani somoni - TJS, - /// Turkmenistan manat - TMT, - /// Tunisian dinar - TND, - /// Tongan paสปanga - TOP, - /// Turkish lira - TRY, - /// Trinidad and Tobago dollar - TTD, - /// New Taiwan dollar - TWD, - /// Tanzanian shilling - TZS, - /// Ukrainian hryvnia - UAH, - /// Ugandan shilling - UGX, - /// United States dollar - USD, - /// United States dollar (next day) (funds code) - USN, - /// Uruguay Peso en Unidades Indexadas (URUIURUI) (funds code) - UYI, - /// Uruguayan peso - UYU, - /// Unidad previsional - UYW, - /// Uzbekistan som - UZS, - /// Venezuelan bolรญvar soberano - VES, - /// Vietnamese ฤ‘แป“ng - VND, - /// Vanuatu vatu - VUV, - /// Samoan tala - WST, - /// CFA franc BEAC - XAF, - /// Silver (one troy ounce) - XAG, - /// Gold (one troy ounce) - XAU, - /// European Composite Unit (EURCO) (bond market unit) - XBA, - /// European Monetary Unit (E.M.U.-6) (bond market unit) - XBB, - /// European Unit of Account 9 (E.U.A.-9) (bond market unit) - XBC, - /// European Unit of Account 17 (E.U.A.-17) (bond market unit) - XBD, - /// East Caribbean dollar - XCD, - /// Special drawing rights - XDR, - /// CFA franc BCEAO - XOF, - /// Palladium (one troy ounce) - XPD, - /// CFP franc (franc Pacifique) - XPF, - /// Platinum (one troy ounce) - XPT, - /// SUCRE - XSU, - /// Code reserved for testing - XTS, - /// ADB Unit of Account - XUA, - /// No currency - XXX, - /// Yemeni rial - YER, - /// South African rand - ZAR, - /// Zambian kwacha - ZMW, - /// Zimbabwean dollar - ZWL, -} diff --git a/crates/teloxide-core/src/types/parse_mode.rs b/crates/teloxide-core/src/types/parse_mode.rs index 0edc69eb..0ac230f8 100644 --- a/crates/teloxide-core/src/types/parse_mode.rs +++ b/crates/teloxide-core/src/types/parse_mode.rs @@ -55,6 +55,11 @@ use serde::{Deserialize, Serialize}; /// ```rust #[doc = "pre-formatted fixed-width code block written in the Rust programming language"] /// ``` +/// >Block quotation started +/// >Block quotation continued +/// >Block quotation continued +/// >Block quotation continued +/// >The last line of the block quotation /// ```` /// /// Please note: @@ -98,6 +103,8 @@ use serde::{Deserialize, Serialize}; ///
pre-formatted fixed-width code block
#[doc = "
pre-formatted fixed-width code block written in the \
          Rust programming language
"] +///
Block quotation started\nBlock quotation continued\nThe last +/// line of the block quotation
/// ```` /// /// Please note: diff --git a/crates/teloxide-core/src/types/poll_answer.rs b/crates/teloxide-core/src/types/poll_answer.rs index 5fb670b0..706c4062 100644 --- a/crates/teloxide-core/src/types/poll_answer.rs +++ b/crates/teloxide-core/src/types/poll_answer.rs @@ -1,10 +1,11 @@ use serde::{Deserialize, Deserializer, Serialize}; -use crate::types::{Chat, User}; +use crate::types::{Chat, MaybeAnonymousUser, User}; #[serde_with::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PollAnswer { + // FIXME: PollId /// Unique poll identifier. pub poll_id: String, @@ -14,7 +15,7 @@ pub struct PollAnswer { /// If the voter isn't anonymous, stores the user that changed /// the answer to the poll #[serde(deserialize_with = "deserialize_voter", flatten)] - pub voter: Voter, + pub voter: MaybeAnonymousUser, /// 0-based identifiers of answer options, chosen by the user. /// @@ -22,31 +23,6 @@ pub struct PollAnswer { pub option_ids: Vec, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum Voter { - Chat(Chat), - User(User), -} - -impl Voter { - #[must_use] - pub fn chat(&self) -> Option<&Chat> { - match self { - Self::Chat(chat) => Some(chat), - _ => None, - } - } - - #[must_use] - pub fn user(&self) -> Option<&User> { - match self { - Self::User(user) => Some(user), - _ => None, - } - } -} - /// These fields `chat` and `user` from the original [`PollAnswer`] should be /// exclusive, but in cases when the `voter_chat` is presented the `user` isn't /// `None`, but rather actual value for backward compatibility, the field `user` @@ -61,9 +37,9 @@ struct VoterDe { pub user: Option, } -fn deserialize_voter<'d, D: Deserializer<'d>>(d: D) -> Result { +fn deserialize_voter<'d, D: Deserializer<'d>>(d: D) -> Result { let VoterDe { voter_chat, user } = VoterDe::deserialize(d)?; - Ok(voter_chat.map(Voter::Chat).or(user.map(Voter::User)).unwrap()) + Ok(voter_chat.map(MaybeAnonymousUser::Chat).or(user.map(MaybeAnonymousUser::User)).unwrap()) } #[cfg(test)] @@ -71,21 +47,22 @@ mod tests { use super::*; #[test] - fn test_poll_answer_with_user_de() { + fn poll_answer_with_user_de() { let json = r#"{ - "poll_id":"POLL_ID", - "user": {"id":42,"is_bot":false,"first_name":"blah"}, + "poll_id": "POLL_ID", + "user": {"id": 42,"is_bot": false,"first_name": "blah"}, "option_ids": [] }"#; let poll_answer: PollAnswer = serde_json::from_str(json).unwrap(); - assert!(matches!(poll_answer.voter, Voter::User(_))); + + assert!(poll_answer.voter.is_user()); } #[test] - fn test_poll_answer_with_voter_chat_de() { + fn poll_answer_with_voter_chat_de() { let json = r#"{ - "poll_id":"POLL_ID", + "poll_id": "POLL_ID", "voter_chat": { "id": -1001160242915, "title": "a", @@ -95,11 +72,11 @@ mod tests { }"#; let poll_answer: PollAnswer = serde_json::from_str(json).unwrap(); - assert!(matches!(poll_answer.voter, Voter::Chat(_))); + assert!(poll_answer.voter.is_chat()); } #[test] - fn test_poll_answer_with_both_user_and_voter_chat_de() { + fn poll_answer_with_both_user_and_voter_chat_de() { let json = r#"{ "poll_id":"POLL_ID", "voter_chat": { @@ -107,11 +84,11 @@ mod tests { "title": "a", "type": "group" }, - "user": {"id":136817688,"is_bot":true,"first_name":"Channel_Bot"}, + "user": {"id": 136817688,"is_bot": true,"first_name": "Channel_Bot"}, "option_ids": [] }"#; let poll_answer: PollAnswer = serde_json::from_str(json).unwrap(); - assert!(matches!(poll_answer.voter, Voter::Chat(_))); + assert!(poll_answer.voter.is_chat()); } } diff --git a/crates/teloxide-core/src/types/pre_checkout_query.rs b/crates/teloxide-core/src/types/pre_checkout_query.rs index 10628626..0dc24b08 100644 --- a/crates/teloxide-core/src/types/pre_checkout_query.rs +++ b/crates/teloxide-core/src/types/pre_checkout_query.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::types::{Currency, OrderInfo, User}; +use crate::types::{OrderInfo, User}; /// This object contains information about an incoming pre-checkout query. /// @@ -14,10 +14,12 @@ pub struct PreCheckoutQuery { /// User who sent the query. pub from: User, - /// Three-letter ISO 4217 [currency] code. + /// Three-letter ISO 4217 currency code, see [more on currencies]. Pass + /// `XTR` for payments in [Telegram Stars]. /// - /// [currency]: https://core.telegram.org/bots/payments#supported-currencies - pub currency: Currency, + /// [more on currencies]: https://core.telegram.org/bots/payments#supported-currencies + /// [Telegram Stars]: https://t.me/BotNews/90 + pub currency: String, /// Total price in the _smallest units_ of the currency (integer, **not** /// float/double). For example, for a price of `US$ 1.45` pass `amount = diff --git a/crates/teloxide-core/src/types/reaction_type.rs b/crates/teloxide-core/src/types/reaction_type.rs new file mode 100644 index 00000000..9ba328fb --- /dev/null +++ b/crates/teloxide-core/src/types/reaction_type.rs @@ -0,0 +1,43 @@ +use serde::{Deserialize, Serialize}; + +/// The reaction type is based on an emoji or custom emoji. +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum ReactionType { + /// Emoji reaction. + Emoji { + /// Reaction emoji. Currently, it can be one of "๐Ÿ‘", "๐Ÿ‘Ž", "โค", "๐Ÿ”ฅ", + /// "๐Ÿฅฐ", "๐Ÿ‘", "๐Ÿ˜", "๐Ÿค”", "๐Ÿคฏ", "๐Ÿ˜ฑ", "๐Ÿคฌ", "๐Ÿ˜ข", "๐ŸŽ‰", "๐Ÿคฉ", + /// "๐Ÿคฎ", "๐Ÿ’ฉ", "๐Ÿ™", "๐Ÿ‘Œ", "๐Ÿ•Š", "๐Ÿคก", "๐Ÿฅฑ", "๐Ÿฅด", "๐Ÿ˜", "๐Ÿณ", + /// "โคโ€๐Ÿ”ฅ", "๐ŸŒš", "๐ŸŒญ", "๐Ÿ’ฏ", "๐Ÿคฃ", "โšก", "๐ŸŒ", "๐Ÿ†", "๐Ÿ’”", "๐Ÿคจ", + /// "๐Ÿ˜", "๐Ÿ“", "๐Ÿพ", "๐Ÿ’‹", "๐Ÿ–•", "๐Ÿ˜ˆ", "๐Ÿ˜ด", "๐Ÿ˜ญ", "๐Ÿค“", "๐Ÿ‘ป", + /// "๐Ÿ‘จโ€๐Ÿ’ป", "๐Ÿ‘€", "๐ŸŽƒ", "๐Ÿ™ˆ", "๐Ÿ˜‡", "๐Ÿ˜จ", "๐Ÿค", "โœ", "๐Ÿค—", "๐Ÿซก", + /// "๐ŸŽ…", "๐ŸŽ„", "โ˜ƒ", "๐Ÿ’…", "๐Ÿคช", "๐Ÿ—ฟ", "๐Ÿ†’", "๐Ÿ’˜", "๐Ÿ™‰", "๐Ÿฆ„", "๐Ÿ˜˜", + /// "๐Ÿ’Š", "๐Ÿ™Š", "๐Ÿ˜Ž", "๐Ÿ‘พ", "๐Ÿคทโ€โ™‚", "๐Ÿคท", "๐Ÿคทโ€โ™€", "๐Ÿ˜ก" + emoji: String, + }, + /// Custom emoji reaction. + CustomEmoji { + /// Custom emoji identifier. + custom_emoji_id: String, + }, +} + +impl ReactionType { + #[must_use] + pub fn emoji(&self) -> Option<&String> { + match &self { + Self::Emoji { emoji } => Some(emoji), + _ => None, + } + } + + #[must_use] + pub fn custom_emoji_id(&self) -> Option<&String> { + match &self { + Self::CustomEmoji { custom_emoji_id } => Some(custom_emoji_id), + _ => None, + } + } +} diff --git a/crates/teloxide-core/src/types/reply_keyboard_markup.rs b/crates/teloxide-core/src/types/reply_keyboard_markup.rs index e409f469..4b38b2df 100644 --- a/crates/teloxide-core/src/types/reply_keyboard_markup.rs +++ b/crates/teloxide-core/src/types/reply_keyboard_markup.rs @@ -22,14 +22,14 @@ pub struct KeyboardMarkup { /// Requests clients to always show the keyboard when the regular keyboard /// is hidden. Defaults to `false`, in which case the custom keyboard /// can be hidden and opened with a keyboard icon. - #[serde(skip_serializing_if = "std::ops::Not::not")] + #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub is_persistent: bool, /// Requests clients to resize the keyboard vertically for optimal fit /// (e.g., make the keyboard smaller if there are just two rows of /// buttons). Defaults to `false`, in which case the custom keyboard is /// always of the same height as the app's standard keyboard. - #[serde(skip_serializing_if = "std::ops::Not::not")] + #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub resize_keyboard: bool, /// Requests clients to hide the keyboard as soon as it's been used. The @@ -37,12 +37,12 @@ pub struct KeyboardMarkup { /// display the usual letter-keyboard in the chat โ€“ the user can press a /// special button in the input field to see the custom keyboard again. /// Defaults to `false`. - #[serde(skip_serializing_if = "std::ops::Not::not")] + #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub one_time_keyboard: bool, /// The placeholder to be shown in the input field when the keyboard is /// active; 1-64 characters. - #[serde(skip_serializing_if = "str::is_empty")] + #[serde(default, skip_serializing_if = "str::is_empty")] pub input_field_placeholder: String, /// Use this parameter if you want to show the keyboard to specific users @@ -55,7 +55,7 @@ pub struct KeyboardMarkup { /// in the group donโ€™t see the keyboard. /// /// [`Message`]: crate::types::Message - #[serde(skip_serializing_if = "std::ops::Not::not")] + #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub selective: bool, } @@ -129,3 +129,23 @@ impl KeyboardMarkup { Self { selective: true, ..self } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "keyboard": [[{"text": "a"}, {"text": "b"}], [{"text": "c"}, {"text": "d"}]], + "input_field_placeholder": "", + "is_persistent": true, + "one_time_keyboard": false, + "resize_keyboard": true, + "selective": false + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/reply_keyboard_remove.rs b/crates/teloxide-core/src/types/reply_keyboard_remove.rs index 3c08ad9c..0fe6a6fe 100644 --- a/crates/teloxide-core/src/types/reply_keyboard_remove.rs +++ b/crates/teloxide-core/src/types/reply_keyboard_remove.rs @@ -34,7 +34,7 @@ pub struct KeyboardRemove { /// showing the keyboard with poll options to users who haven't voted yet. /// /// [`Message`]: crate::types::Message - #[serde(skip_serializing_if = "std::ops::Not::not")] + #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub selective: bool, } @@ -52,3 +52,19 @@ impl KeyboardRemove { Self { selective: true, ..self } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "remove_keyboard": true, + "selective": false + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/reply_parameters.rs b/crates/teloxide-core/src/types/reply_parameters.rs new file mode 100644 index 00000000..6baa8ed9 --- /dev/null +++ b/crates/teloxide-core/src/types/reply_parameters.rs @@ -0,0 +1,65 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{MessageEntity, MessageId, Recipient}; + +/// Describes reply parameters for the message that is being sent. +#[serde_with::skip_serializing_none] +#[derive(Default, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct ReplyParameters { + /// Identifier of the message that will be replied to in the current chat, + /// or in the chat _chat\_id_ if it is specified + // Issue https://github.com/teloxide/teloxide/issues/1135 + #[serde(with = "crate::types::msg_id_as_int")] + pub message_id: MessageId, + /// If the message to be replied to is from a different chat, unique + /// identifier for the chat or username of the channel (in the format + /// `@channelusername`). Not supported for messages sent on behalf of a + /// business account. + pub chat_id: Option, + /// Pass _true_ if the message should be sent even if the specified message + /// to be replied to is not found; can be used only for replies in the + /// same chat and forum topic. Always `false` for replies in another chat or + /// forum topic. Always `true` for messages sent on behalf of a business + /// account. + pub allow_sending_without_reply: Option, + /// Quoted part of the message to be replied to; 0-1024 characters after + /// entities parsing. The quote must be an exact substring of the message to + /// be replied to, including _bold_, _italic_, _underline_, _strikethrough_, + /// _spoiler_, and _custom_emoji_ entities. The message will fail to send if + /// the quote isn't found in the original message. + pub quote: Option, + /// Mode for parsing entities in the quote. See [formatting options] for + /// more details. + /// + /// [formatting options]: https://core.telegram.org/bots/api#formatting-options + pub quote_parse_mode: Option, + /// A JSON-serialized list of special entities that appear in the quote. It + /// can be specified instead of quote_parse_mode. + pub quote_entities: Option>, + /// Position of the quote in the original message in UTF-16 code units + pub quote_position: Option, +} + +impl ReplyParameters { + pub fn new(message_id: MessageId) -> Self { + Self { message_id, ..Self::default() } + } + + /// Setter for the `chat_id` field + pub fn chat_id(mut self, chat_id: Recipient) -> Self { + self.chat_id = Some(chat_id); + self + } + + /// Sets the `allow_sending_without_reply_field` to _true_ + pub fn allow_sending_without_reply(mut self) -> Self { + self.allow_sending_without_reply = Some(true); + self + } + + /// Setter for the `quote` field + pub fn quote(mut self, quote: String) -> Self { + self.quote = Some(quote); + self + } +} diff --git a/crates/teloxide-core/src/types/request_id.rs b/crates/teloxide-core/src/types/request_id.rs new file mode 100644 index 00000000..98cd87f3 --- /dev/null +++ b/crates/teloxide-core/src/types/request_id.rs @@ -0,0 +1,24 @@ +use derive_more::Display; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[serde(transparent)] +pub struct RequestId(pub i32); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_request_id_de() { + #[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)] + struct Dummy { + request_id: RequestId, + } + let json = r#"{"request_id":42}"#; + let dummy = Dummy { request_id: RequestId(42) }; + + assert_eq!(serde_json::to_string(&dummy).unwrap(), json); + assert_eq!(dummy, serde_json::from_str(json).unwrap()); + } +} diff --git a/crates/teloxide-core/src/types/rgb.rs b/crates/teloxide-core/src/types/rgb.rs new file mode 100644 index 00000000..22b4b568 --- /dev/null +++ b/crates/teloxide-core/src/types/rgb.rs @@ -0,0 +1,113 @@ +use rgb::RGB8; +use serde::{de::Visitor, Deserialize, Serialize}; + +/// RGB color format +#[repr(C)] +#[derive(Clone, Copy, Debug)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Rgb { + pub r: u8, + pub g: u8, + pub b: u8, +} + +impl Rgb { + /// Convert a [`Rgb`] struct into a big endian `u32` representing the RGB + /// color. + /// + /// # Example + /// + /// ``` + /// use teloxide_core::types::Rgb; + /// assert_eq!(Rgb { r: 0xAA, g: 0xBB, b: 0xCC }.to_u32(), 0xAABBCC); + /// ``` + /// + /// [`Rgb`]: Rgb + pub fn to_u32(self) -> u32 { + u32::from_be_bytes([0, self.r, self.g, self.b]) + } + + /// Convert a big endian `u32` representing the RGB color into a [`Rgb`] + /// struct. + /// + /// # Example + /// + /// ``` + /// use teloxide_core::types::Rgb; + /// assert_eq!(Rgb::from_u32(0xAABBCC), Rgb { r: 0xAA, g: 0xBB, b: 0xCC }); + /// ``` + /// + /// [`Rgb`]: Rgb + pub fn from_u32(rgb: u32) -> Self { + let [_, r, g, b] = rgb.to_be_bytes(); + Rgb { r, g, b } + } +} + +impl Serialize for Rgb { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_u32(self.to_u32()) + } +} + +impl<'de> Deserialize<'de> for Rgb { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct V; + + impl Visitor<'_> for V { + type Value = Rgb; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("an integer represeting an RGB color") + } + + fn visit_u32(self, v: u32) -> Result + where + E: serde::de::Error, + { + Ok(Self::Value::from_u32(v)) + } + + fn visit_u64(self, v: u64) -> Result + where + E: serde::de::Error, + { + self.visit_u32(v.try_into().map_err(|_| E::custom("rgb value doesn't fit u32"))?) + } + } + + deserializer.deserialize_u32(V) + } +} + +impl From for Rgb { + fn from(color: RGB8) -> Self { + Rgb { r: color.r, g: color.g, b: color.b } + } +} + +#[cfg(test)] +mod tests { + use serde::{Deserialize, Serialize}; + + use super::*; + + #[test] + fn json() { + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct Struct { + color: Rgb, + } + + let json = format!(r#"{{"color":{}}}"#, 0x00AABBCC); + let Struct { color } = serde_json::from_str(&json).unwrap(); + + assert_eq!(color, Rgb { r: 0xAA, g: 0xBB, b: 0xCC }) + } +} diff --git a/crates/teloxide-core/src/types/shared_user.rs b/crates/teloxide-core/src/types/shared_user.rs new file mode 100644 index 00000000..bca2dcb9 --- /dev/null +++ b/crates/teloxide-core/src/types/shared_user.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{PhotoSize, UserId}; + +/// This object contains information about a user that was shared with the bot +/// using a [`KeyboardButtonRequestUsers`] button. +/// +/// [`KeyboardButtonRequestUsers`]: crate::types::KeyboardButtonRequestUsers +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct SharedUser { + /// Identifier of the shared user + pub user_id: UserId, + + /// First name of the user, if it was requested by the bot + pub first_name: Option, + + /// Last name of the user, if it was requested by the bot + pub last_name: Option, + + /// Username of the user, if it was requested by the bot + pub username: Option, + + /// Available sizes of the chat photo, if it was requested + pub photo: Option>, +} diff --git a/crates/teloxide-core/src/types/sticker_set.rs b/crates/teloxide-core/src/types/sticker_set.rs index 12c92bb1..32b9f379 100644 --- a/crates/teloxide-core/src/types/sticker_set.rs +++ b/crates/teloxide-core/src/types/sticker_set.rs @@ -2,7 +2,7 @@ use std::ops::Deref; use serde::{Deserialize, Serialize}; -use crate::types::{PhotoSize, Sticker, StickerFormat, StickerFormatFlags, StickerType}; +use crate::types::{PhotoSize, Sticker, StickerType}; /// This object represents a sticker set. /// @@ -20,11 +20,6 @@ pub struct StickerSet { #[serde(flatten)] pub kind: StickerType, - // FIXME: remove it in 7.2 https://core.telegram.org/bots/api#march-31-2024 - /// Sticker format flags shared by all stickers in this set. - #[serde(flatten)] - pub flags: StickerFormatFlags, - /// List of all set stickers. pub stickers: Vec, @@ -50,66 +45,6 @@ impl Deref for StickerSet { } } -impl StickerSet { - // FIXME: remove deprecation, when it will be a way to determine the format of - // the sticker set - /// Returns the format of the stickers in this set - /// - /// Note: this method currently is useless, so the format is always - /// StickerFormat::Static - #[must_use] - #[deprecated(note = "TBA7.2 brought the breaking change: flags 'is_video' and 'is_animated' \ - were removed, so currently there is no way to determine the format of \ - the sticker set in the currently supported version (TBA6.6)")] - pub fn format(&self) -> StickerFormat { - self.flags.format() - } - - /// Returns `true` is this is a "normal" raster sticker. - /// - /// Alias to [`self.format().is_static()`]. - /// - /// [`self.format().is_static()`]: StickerFormat::is_static - #[must_use] - #[deprecated(note = "TBA7.2 brought the breaking change: flags 'is_video' and 'is_animated' \ - were removed, so currently there is no way to determine the format of \ - the sticker set in the currently supported version (TBA6.6)")] - pub fn is_static(&self) -> bool { - #[allow(deprecated)] - self.format().is_static() - } - - /// Returns `true` is this is an [animated] sticker. - /// - /// Alias to [`self.format().is_animated()`]. - /// - /// [`self.format().is_animated()`]: StickerFormat::is_animated - /// [animated]: https://telegram.org/blog/animated-stickers - #[must_use] - #[deprecated(note = "TBA7.2 brought the breaking change: flags 'is_video' and 'is_animated' \ - were removed, so currently there is no way to determine the format of \ - the sticker set in the currently supported version (TBA6.6)")] - pub fn is_animated(&self) -> bool { - #[allow(deprecated)] - self.format().is_animated() - } - - /// Returns `true` is this is a [video] sticker. - /// - /// Alias to [`self.format().is_video()`]. - /// - /// [`self.format().is_video()`]: StickerFormat::is_video - /// [video]: https://telegram.org/blog/video-stickers-better-reactions - #[must_use] - #[deprecated(note = "TBA7.2 brought the breaking change: flags 'is_video' and 'is_animated' \ - were removed, so currently there is no way to determine the format of \ - the sticker set in the currently supported version (TBA6.6)")] - pub fn is_video(&self) -> bool { - #[allow(deprecated)] - self.format().is_video() - } -} - #[cfg(test)] mod tests { use crate::types::StickerSet; diff --git a/crates/teloxide-core/src/types/story.rs b/crates/teloxide-core/src/types/story.rs index a59e1568..abe63eac 100644 --- a/crates/teloxide-core/src/types/story.rs +++ b/crates/teloxide-core/src/types/story.rs @@ -1,5 +1,81 @@ use serde::{Deserialize, Serialize}; -/// TBA 6.8: currently it holds no information +use crate::types::{Chat, ChatKind, StoryId}; + +/// This object represents a story. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Story {} +pub struct Story { + /// Unique identifier for the story in the chat. + pub id: StoryId, + + /// Chat that posted the story. + pub chat: Chat, +} + +impl Story { + /// Returns an URL that links to the story with it's id and chat username in + /// the form of `tg://resolve?domain=<โ€ฆ>&story=<โ€ฆ>`. + #[must_use] + pub fn url(&self) -> Option { + let username = match &self.chat.kind { + ChatKind::Public(c) => match &c.kind { + super::PublicChatKind::Channel(c) => c.username.as_ref(), + super::PublicChatKind::Group(_) => None, + super::PublicChatKind::Supergroup(g) => g.username.as_ref(), + }, + ChatKind::Private(c) => c.username.as_ref(), + }; + username.map(|username| { + reqwest::Url::parse(&format!("tg://resolve?domain={username}&story={}", self.id)) + .unwrap() + }) + } +} + +#[cfg(test)] +mod tests { + use crate::types::{ + Chat, ChatFullInfo, ChatId, ChatKind, ChatPublic, PublicChatKind, PublicChatSupergroup, + Story, StoryId, + }; + + #[test] + fn url_works() { + let story = Story { + chat: Chat { + id: ChatId(-1001389841361), + kind: ChatKind::Public(Box::new(ChatPublic { + title: Some("GNOME".to_owned()), + kind: PublicChatKind::Supergroup(PublicChatSupergroup { + username: Some("gnome_ru".to_owned()), + active_usernames: None, + is_forum: false, + sticker_set_name: None, + can_set_sticker_set: None, + custom_emoji_sticker_set_name: None, + permissions: None, + slow_mode_delay: None, + unrestrict_boost_count: None, + linked_chat_id: None, + location: None, + join_to_send_messages: None, + join_by_request: None, + }), + description: None, + invite_link: None, + has_protected_content: None, + })), + photo: None, + available_reactions: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo::default(), + }, + id: StoryId(420), + }; + + assert_eq!(story.url().unwrap(), "tg://resolve?domain=gnome_ru&story=420".parse().unwrap()); + } +} diff --git a/crates/teloxide-core/src/types/story_id.rs b/crates/teloxide-core/src/types/story_id.rs new file mode 100644 index 00000000..59dcdee9 --- /dev/null +++ b/crates/teloxide-core/src/types/story_id.rs @@ -0,0 +1,29 @@ +use serde::{Deserialize, Serialize}; + +/// Identifier of a story. +#[derive(Clone, Copy)] +#[derive(Debug, derive_more::Display)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Serialize, Deserialize)] +#[serde(transparent)] +pub struct StoryId(pub u64); + +#[cfg(test)] +mod tests { + use super::*; + + /// Test that `StoryId` is serialized as the underlying integer + #[test] + fn deser() { + let story_id = S { id: StoryId(17) }; + let json = r#"{"id":17}"#; + + #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] + struct S { + id: StoryId, + } + + assert_eq!(serde_json::to_string(&story_id).unwrap(), json); + assert_eq!(story_id, serde_json::from_str(json).unwrap()); + } +} diff --git a/crates/teloxide-core/src/types/successful_payment.rs b/crates/teloxide-core/src/types/successful_payment.rs index 8f5a4144..fbd81e6d 100644 --- a/crates/teloxide-core/src/types/successful_payment.rs +++ b/crates/teloxide-core/src/types/successful_payment.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::types::{Currency, OrderInfo}; +use crate::types::OrderInfo; /// This object contains basic information about a successful payment. /// @@ -8,10 +8,12 @@ use crate::types::{Currency, OrderInfo}; #[serde_with::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct SuccessfulPayment { - /// Three-letter ISO 4217 [currency] code. + /// Three-letter ISO 4217 currency code, see [more on currencies]. Pass + /// `XTR` for payments in [Telegram Stars]. /// - /// [currency]: https://core.telegram.org/bots/payments#supported-currencies - pub currency: Currency, + /// [more on currencies]: https://core.telegram.org/bots/payments#supported-currencies + /// [Telegram Stars]: https://t.me/BotNews/90 + pub currency: String, /// Total price in the smallest units of the currency (integer, not /// float/double). For example, for a price of `US$ 1.45` pass `amount = diff --git a/crates/teloxide-core/src/types/text_quote.rs b/crates/teloxide-core/src/types/text_quote.rs new file mode 100644 index 00000000..eda8811e --- /dev/null +++ b/crates/teloxide-core/src/types/text_quote.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::MessageEntity; + +/// This object contains information about the quoted part of a message that is +/// replied to by the given message. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct TextQuote { + /// Text of the quoted part of a message that is replied to by the given + /// message + pub text: String, + /// Special entities that appear in the quote. Currently, only _bold_, + /// _italic_, _underline_, _strikethrough_, _spoiler_, and + /// _custom_emoji_ entities are kept in quotes. + #[serde(default)] + pub entities: Vec, + /// Approximate quote position in the original message in UTF-16 code units + /// as specified by the sender + pub position: u32, + /// True, if the quote was chosen manually by the message sender. Otherwise, + /// the quote was added automatically by the server. + #[serde(default)] + pub is_manual: bool, +} diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index cd08a326..dd06de27 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -3,8 +3,10 @@ use serde::{de::MapAccess, Deserialize, Serialize, Serializer}; use serde_json::Value; use crate::types::{ - CallbackQuery, Chat, ChatJoinRequest, ChatMemberUpdated, ChosenInlineResult, InlineQuery, - Message, Poll, PollAnswer, PreCheckoutQuery, ShippingQuery, User, + BusinessConnection, BusinessMessagesDeleted, CallbackQuery, Chat, ChatBoostRemoved, + ChatBoostUpdated, ChatJoinRequest, ChatMemberUpdated, ChosenInlineResult, InlineQuery, Message, + MessageReactionCountUpdated, MessageReactionUpdated, Poll, PollAnswer, PreCheckoutQuery, + ShippingQuery, User, }; /// This [object] represents an incoming update. @@ -59,6 +61,37 @@ pub enum UpdateKind { /// New version of a channel post that is known to the bot and was edited. EditedChannelPost(Message), + /// The bot was connected to or disconnected from a business account, or a + /// user edited an existing connection with the bot + BusinessConnection(BusinessConnection), + + /// New non-service message from a connected business account + BusinessMessage(Message), + + /// New version of a message from a connected business account + EditedBusinessMessage(Message), + + /// Messages were deleted from a connected business account + DeletedBusinessMessages(BusinessMessagesDeleted), + + /// A reaction to a message was changed by a user. The bot must be an + /// administrator in the chat and must explicitly specify + /// [`AllowedUpdate::MessageReaction`] in the list of `allowed_updates` + /// to receive these updates. The update isn't received for reactions + /// set by bots. + /// + /// [`AllowedUpdate::MessageReaction`]: crate::types::AllowedUpdate::MessageReaction + MessageReaction(MessageReactionUpdated), + + /// Reactions to a message with anonymous reactions were changed. The bot + /// must be an administrator in the chat and must explicitly specify + /// [`AllowedUpdate::MessageReactionCount`] in the list of `allowed_updates` + /// to receive these updates. The updates are grouped and can be sent + /// with delay up to a few minutes. + /// + /// [`AllowedUpdate::MessageReactionCount`]: crate::types::AllowedUpdate::MessageReactionCount + MessageReactionCount(MessageReactionCountUpdated), + /// New incoming [inline] query. /// /// [inline]: https://core.telegram.org/bots/api#inline-mode @@ -108,6 +141,14 @@ pub enum UpdateKind { /// updates. ChatJoinRequest(ChatJoinRequest), + /// A chat boost was added or changed. The bot must be an administrator in + /// the chat to receive these updates. + ChatBoost(ChatBoostUpdated), + + /// A boost was removed from a chat. The bot must be an administrator in the + /// chat to receive these updates. + RemovedChatBoost(ChatBoostRemoved), + /// An error that happened during deserialization. /// /// This allows `teloxide` to continue working even if telegram adds a new @@ -129,10 +170,18 @@ impl Update { use UpdateKind::*; let from = match &self.kind { - Message(m) | EditedMessage(m) | ChannelPost(m) | EditedChannelPost(m) => m.from()?, + Message(m) + | EditedMessage(m) + | ChannelPost(m) + | EditedChannelPost(m) + | BusinessMessage(m) + | EditedBusinessMessage(m) => m.from.as_ref()?, + + BusinessConnection(conn) => &conn.user, CallbackQuery(query) => &query.from, ChosenInlineResult(chosen) => &chosen.from, + MessageReaction(reaction) => return reaction.user(), InlineQuery(query) => &query.from, ShippingQuery(query) => &query.from, PreCheckoutQuery(query) => &query.from, @@ -140,8 +189,12 @@ impl Update { MyChatMember(m) | ChatMember(m) => &m.from, ChatJoinRequest(r) => &r.from, + ChatBoost(b) => return b.boost.source.user(), + RemovedChatBoost(b) => return b.source.user(), - Poll(_) | Error(_) => return None, + MessageReactionCount(_) | DeletedBusinessMessages(_) | Poll(_) | Error(_) => { + return None + } }; Some(from) @@ -189,7 +242,16 @@ impl Update { UpdateKind::Message(message) | UpdateKind::EditedMessage(message) | UpdateKind::ChannelPost(message) - | UpdateKind::EditedChannelPost(message) => i0(message.mentioned_users()), + | UpdateKind::EditedChannelPost(message) + | UpdateKind::BusinessMessage(message) + | UpdateKind::EditedBusinessMessage(message) => i0(message.mentioned_users()), + + UpdateKind::MessageReaction(answer) => { + if let Some(user) = answer.user() { + return i1(once(user)); + } + i6(empty()) + } UpdateKind::InlineQuery(query) => i1(once(&query.from)), UpdateKind::ChosenInlineResult(query) => i1(once(&query.from)), @@ -209,7 +271,24 @@ impl Update { i4(member.mentioned_users()) } UpdateKind::ChatJoinRequest(request) => i5(request.mentioned_users()), - UpdateKind::Error(_) => i6(empty()), + + UpdateKind::ChatBoost(b) => { + if let Some(user) = b.boost.source.user() { + return i1(once(user)); + } + i6(empty()) + } + UpdateKind::RemovedChatBoost(b) => { + if let Some(user) = b.source.user() { + return i1(once(user)); + } + i6(empty()) + } + + UpdateKind::MessageReactionCount(_) + | UpdateKind::BusinessConnection(_) + | UpdateKind::DeletedBusinessMessages(_) + | UpdateKind::Error(_) => i6(empty()), } } @@ -219,13 +298,24 @@ impl Update { use UpdateKind::*; let chat = match &self.kind { - Message(m) | EditedMessage(m) | ChannelPost(m) | EditedChannelPost(m) => &m.chat, - CallbackQuery(q) => &q.message.as_ref()?.chat, + Message(m) + | EditedMessage(m) + | ChannelPost(m) + | EditedChannelPost(m) + | BusinessMessage(m) + | EditedBusinessMessage(m) => &m.chat, + CallbackQuery(q) => q.message.as_ref()?.chat(), ChatMember(m) => &m.chat, MyChatMember(m) => &m.chat, ChatJoinRequest(c) => &c.chat, + MessageReaction(r) => &r.chat, + MessageReactionCount(r) => &r.chat, + ChatBoost(b) => &b.chat, + RemovedChatBoost(b) => &b.chat, + DeletedBusinessMessages(m) => &m.chat, InlineQuery(_) + | BusinessConnection(_) | ChosenInlineResult(_) | ShippingQuery(_) | PreCheckoutQuery(_) @@ -293,6 +383,28 @@ impl<'de> Deserialize<'de> for UpdateKind { "edited_channel_post" => { map.next_value::().ok().map(UpdateKind::EditedChannelPost) } + "business_connection" => map + .next_value::() + .ok() + .map(UpdateKind::BusinessConnection), + "business_message" => { + map.next_value::().ok().map(UpdateKind::BusinessMessage) + } + "edited_business_message" => { + map.next_value::().ok().map(UpdateKind::EditedBusinessMessage) + } + "deleted_business_messages" => map + .next_value::() + .ok() + .map(UpdateKind::DeletedBusinessMessages), + "message_reaction" => map + .next_value::() + .ok() + .map(UpdateKind::MessageReaction), + "message_reaction_count" => map + .next_value::() + .ok() + .map(UpdateKind::MessageReactionCount), "inline_query" => { map.next_value::().ok().map(UpdateKind::InlineQuery) } @@ -324,6 +436,13 @@ impl<'de> Deserialize<'de> for UpdateKind { .next_value::() .ok() .map(UpdateKind::ChatJoinRequest), + "chat_boost" => { + map.next_value::().ok().map(UpdateKind::ChatBoost) + } + "removed_chat_boost" => map + .next_value::() + .ok() + .map(UpdateKind::RemovedChatBoost), _ => Some(empty_error()), }) .unwrap_or_else(empty_error); @@ -351,27 +470,49 @@ impl Serialize for UpdateKind { UpdateKind::EditedChannelPost(v) => { s.serialize_newtype_variant(name, 3, "edited_channel_post", v) } - UpdateKind::InlineQuery(v) => s.serialize_newtype_variant(name, 4, "inline_query", v), + UpdateKind::BusinessConnection(v) => { + s.serialize_newtype_variant(name, 4, "business_connection", v) + } + UpdateKind::BusinessMessage(v) => { + s.serialize_newtype_variant(name, 5, "business_message", v) + } + UpdateKind::EditedBusinessMessage(v) => { + s.serialize_newtype_variant(name, 6, "edited_business_message", v) + } + UpdateKind::DeletedBusinessMessages(v) => { + s.serialize_newtype_variant(name, 7, "deleted_business_messages", v) + } + UpdateKind::MessageReaction(v) => { + s.serialize_newtype_variant(name, 8, "message_reaction", v) + } + UpdateKind::MessageReactionCount(v) => { + s.serialize_newtype_variant(name, 9, "message_reaction_count", v) + } + UpdateKind::InlineQuery(v) => s.serialize_newtype_variant(name, 10, "inline_query", v), UpdateKind::ChosenInlineResult(v) => { - s.serialize_newtype_variant(name, 5, "chosen_inline_result", v) + s.serialize_newtype_variant(name, 11, "chosen_inline_result", v) } UpdateKind::CallbackQuery(v) => { - s.serialize_newtype_variant(name, 6, "callback_query", v) + s.serialize_newtype_variant(name, 12, "callback_query", v) } UpdateKind::ShippingQuery(v) => { - s.serialize_newtype_variant(name, 7, "shipping_query", v) + s.serialize_newtype_variant(name, 13, "shipping_query", v) } UpdateKind::PreCheckoutQuery(v) => { - s.serialize_newtype_variant(name, 8, "pre_checkout_query", v) + s.serialize_newtype_variant(name, 14, "pre_checkout_query", v) } - UpdateKind::Poll(v) => s.serialize_newtype_variant(name, 9, "poll", v), - UpdateKind::PollAnswer(v) => s.serialize_newtype_variant(name, 10, "poll_answer", v), + UpdateKind::Poll(v) => s.serialize_newtype_variant(name, 15, "poll", v), + UpdateKind::PollAnswer(v) => s.serialize_newtype_variant(name, 16, "poll_answer", v), UpdateKind::MyChatMember(v) => { - s.serialize_newtype_variant(name, 11, "my_chat_member", v) + s.serialize_newtype_variant(name, 17, "my_chat_member", v) } - UpdateKind::ChatMember(v) => s.serialize_newtype_variant(name, 12, "chat_member", v), + UpdateKind::ChatMember(v) => s.serialize_newtype_variant(name, 18, "chat_member", v), UpdateKind::ChatJoinRequest(v) => { - s.serialize_newtype_variant(name, 13, "chat_join_request", v) + s.serialize_newtype_variant(name, 19, "chat_join_request", v) + } + UpdateKind::ChatBoost(v) => s.serialize_newtype_variant(name, 20, "chat_boost", v), + UpdateKind::RemovedChatBoost(v) => { + s.serialize_newtype_variant(name, 21, "removed_chat_boost", v) } UpdateKind::Error(v) => v.serialize(s), } @@ -385,11 +526,16 @@ fn empty_error() -> UpdateKind { #[cfg(test)] mod test { use crate::types::{ - Chat, ChatFullInfo, ChatId, ChatKind, ChatPrivate, MediaKind, MediaText, Message, - MessageCommon, MessageId, MessageKind, Update, UpdateId, UpdateKind, User, UserId, + Chat, ChatBoost, ChatBoostRemoved, ChatBoostSource, ChatBoostSourcePremium, + ChatBoostUpdated, ChatFullInfo, ChatId, ChatKind, ChatPrivate, ChatPublic, + LinkPreviewOptions, MaybeAnonymousUser, MediaKind, MediaText, Message, MessageCommon, + MessageId, MessageKind, MessageReactionCountUpdated, MessageReactionUpdated, + PublicChatChannel, PublicChatKind, PublicChatSupergroup, ReactionCount, ReactionType, + Update, UpdateId, UpdateKind, User, UserId, }; use chrono::DateTime; + use pretty_assertions::assert_eq; // TODO: more tests for deserialization #[test] @@ -415,7 +561,8 @@ mod test { "type":"private" }, "date":1569518342, - "text":"hello there" + "text":"hello there", + "link_preview_options":{"is_disabled":true} } }"#; @@ -425,6 +572,18 @@ mod test { via_bot: None, id: MessageId(6557), thread_id: None, + from: Some(User { + id: UserId(218_485_655), + is_bot: false, + first_name: String::from("Waffle"), + last_name: None, + username: Some(String::from("WaffleLapkin")), + language_code: Some(String::from("en")), + is_premium: false, + added_to_attachment_menu: false, + }), + sender_chat: None, + is_topic_message: false, date, chat: Chat { id: ChatId(218_485_655), @@ -435,39 +594,46 @@ mod test { bio: None, has_private_forwards: None, has_restricted_voice_and_video_messages: None, - emoji_status_custom_emoji_id: None, + personal_chat: None, + birthdate: None, + business_intro: None, + business_location: None, + business_opening_hours: None, }), photo: None, + available_reactions: None, pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None }, + chat_full_info: ChatFullInfo::default(), }, + sender_business_bot: None, kind: MessageKind::Common(MessageCommon { - from: Some(User { - id: UserId(218_485_655), - is_bot: false, - first_name: String::from("Waffle"), - last_name: None, - username: Some(String::from("WaffleLapkin")), - language_code: Some(String::from("en")), - is_premium: false, - added_to_attachment_menu: false, - }), reply_to_message: None, - forward: None, + forward_origin: None, + external_reply: None, + quote: None, + reply_to_story: None, + sender_boost_count: None, edit_date: None, media_kind: MediaKind::Text(MediaText { text: String::from("hello there"), entities: vec![], + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), }), reply_markup: None, - sender_chat: None, author_signature: None, - is_topic_message: false, is_automatic_forward: false, has_protected_content: false, + is_from_offline: false, + business_connection_id: None, }), }), }; @@ -490,7 +656,7 @@ mod test { "from": { "first_name": "Wert", "id": 6962620676, - "is_bot": false, + "is_bot": false, "username": "WertCrypto" }, "message_id": 134545, @@ -532,7 +698,7 @@ mod test { "from": { "first_name": "the Cable Guy", "id": 5964236329, - "is_bot": false, + "is_bot": false, "language_code":"en", "username": "spacewhaleblues" }, @@ -549,7 +715,7 @@ mod test { "from": { "first_name": "Wert", "id": 6962620676, - "is_bot": false, + "is_bot": false, "username": "WertCrypto" }, "message_id": 134545, @@ -726,4 +892,388 @@ mod test { _ => panic!("Expected `MyChatMember`"), } } + + #[test] + fn message_reaction_updated() { + let json = r#" + { + "update_id": 71651249, + "message_reaction": { + "chat": { + "id": -1002184233434, + "title": "Test", + "type": "supergroup" + }, + "message_id": 35, + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + }, + "date": 1721306082, + "old_reaction": [], + "new_reaction": [ + { + "type": "emoji", + "emoji": "๐ŸŒญ" + } + ] + } + } + "#; + + let expected = Update { + id: UpdateId(71651249), + kind: UpdateKind::MessageReaction(MessageReactionUpdated { + chat: Chat { + id: ChatId(-1002184233434), + kind: ChatKind::Public(Box::new(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Supergroup(PublicChatSupergroup { + username: None, + active_usernames: None, + is_forum: false, + sticker_set_name: None, + can_set_sticker_set: None, + custom_emoji_sticker_set_name: None, + permissions: None, + slow_mode_delay: None, + unrestrict_boost_count: None, + linked_chat_id: None, + location: None, + join_to_send_messages: None, + join_by_request: None, + }), + description: None, + invite_link: None, + has_protected_content: None, + })), + photo: None, + available_reactions: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo::default(), + }, + message_id: MessageId(35), + actor: MaybeAnonymousUser::User(User { + id: UserId(1459074222), + is_bot: false, + first_name: "shadowchain".to_owned(), + last_name: None, + username: Some("shdwchn10".to_owned()), + language_code: Some("en".to_owned()), + is_premium: true, + added_to_attachment_menu: false, + }), + date: DateTime::from_timestamp(1721306082, 0).unwrap(), + old_reaction: vec![], + new_reaction: vec![ReactionType::Emoji { emoji: "๐ŸŒญ".to_owned() }], + }), + }; + + let actual = serde_json::from_str::(json).unwrap(); + assert_eq!(expected, actual); + + let json = r#" + { + "update_id": 767844136, + "message_reaction": { + "chat": { + "id": -1002199793788, + "title": "ั‚ะตัั‚", + "type": "supergroup" + }, + "message_id": 2, + "actor_chat": { + "id": -1002199793788, + "title": "ั‚ะตัั‚", + "type": "supergroup" + }, + "date": 1723798597, + "old_reaction": [ + { + "type": "emoji", + "emoji": "โค" + } + ], + "new_reaction": [] + } + } + "#; + let chat = Chat { + id: ChatId(-1002199793788), + kind: ChatKind::Public(Box::new(ChatPublic { + title: Some("ั‚ะตัั‚".to_owned()), + kind: PublicChatKind::Supergroup(PublicChatSupergroup { + username: None, + active_usernames: None, + is_forum: false, + sticker_set_name: None, + can_set_sticker_set: None, + permissions: None, + slow_mode_delay: None, + linked_chat_id: None, + location: None, + join_to_send_messages: None, + join_by_request: None, + custom_emoji_sticker_set_name: None, + unrestrict_boost_count: None, + }), + description: None, + invite_link: None, + has_protected_content: None, + })), + photo: None, + available_reactions: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo::default(), + }; + let expected = Update { + id: UpdateId(767844136), + kind: UpdateKind::MessageReaction(MessageReactionUpdated { + chat: chat.clone(), + message_id: MessageId(2), + actor: MaybeAnonymousUser::Chat(chat), + date: DateTime::from_timestamp(1723798597, 0).unwrap(), + old_reaction: vec![ReactionType::Emoji { emoji: "โค".to_owned() }], + new_reaction: vec![], + }), + }; + + let actual = serde_json::from_str::(json).unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn message_reaction_count_updated() { + let json = r#" + { + "update_id": 71651251, + "message_reaction_count": { + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "message_id": 36, + "date": 1721306391, + "reactions": [ + { + "type": { + "type": "emoji", + "emoji": "๐Ÿ—ฟ" + }, + "total_count": 2 + }, + { + "type": { + "type": "emoji", + "emoji": "๐ŸŒญ" + }, + "total_count": 1 + } + ] + } + } + "#; + + let expected = Update { + id: UpdateId(71651251), + kind: UpdateKind::MessageReactionCount(MessageReactionCountUpdated { + chat: Chat { + id: ChatId(-1002236736395), + kind: ChatKind::Public(Box::new(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Channel(PublicChatChannel { + username: None, + linked_chat_id: None, + }), + description: None, + invite_link: None, + has_protected_content: None, + })), + photo: None, + available_reactions: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo::default(), + }, + message_id: MessageId(36), + date: DateTime::from_timestamp(1721306391, 0).unwrap(), + reactions: vec![ + ReactionCount { + r#type: ReactionType::Emoji { emoji: "๐Ÿ—ฟ".to_owned() }, + total_count: 2, + }, + ReactionCount { + r#type: ReactionType::Emoji { emoji: "๐ŸŒญ".to_owned() }, + total_count: 1, + }, + ], + }), + }; + + let actual = serde_json::from_str::(json).unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn chat_boost_updated() { + let json = r#" + { + "update_id": 71651297, + "chat_boost": { + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "boost": { + "boost_id": "4506e1b7e866e33fcbde78fe1746ec3a", + "add_date": 1721399621, + "expiration_date": 1745088963, + "source": { + "source": "premium", + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + } + } + } + } + } + "#; + + let expected = Update { + id: UpdateId(71651297), + kind: UpdateKind::ChatBoost(ChatBoostUpdated { + chat: Chat { + id: ChatId(-1002236736395), + kind: ChatKind::Public(Box::new(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Channel(PublicChatChannel { + username: None, + linked_chat_id: None, + }), + description: None, + invite_link: None, + has_protected_content: None, + })), + photo: None, + available_reactions: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo::default(), + }, + boost: ChatBoost { + boost_id: "4506e1b7e866e33fcbde78fe1746ec3a".to_owned(), + add_date: DateTime::from_timestamp(1721399621, 0).unwrap(), + expiration_date: DateTime::from_timestamp(1745088963, 0).unwrap(), + source: ChatBoostSource::Premium(ChatBoostSourcePremium { + user: User { + id: UserId(1459074222), + is_bot: false, + first_name: "shadowchain".to_owned(), + last_name: None, + username: Some("shdwchn10".to_owned()), + language_code: Some("en".to_owned()), + is_premium: true, + added_to_attachment_menu: false, + }, + }), + }, + }), + }; + + let actual = serde_json::from_str::(json).unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn chat_boost_removed() { + let json = r#" + { + "update_id": 71651297, + "removed_chat_boost": { + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "boost_id": "4506e1b7e866e33fcbde78fe1746ec3a", + "remove_date": 1721999621, + "source": { + "source": "premium", + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + } + } + } + } + "#; + + let expected = Update { + id: UpdateId(71651297), + kind: UpdateKind::RemovedChatBoost(ChatBoostRemoved { + chat: Chat { + id: ChatId(-1002236736395), + kind: ChatKind::Public(Box::new(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Channel(PublicChatChannel { + username: None, + linked_chat_id: None, + }), + description: None, + invite_link: None, + has_protected_content: None, + })), + photo: None, + available_reactions: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo::default(), + }, + boost_id: "4506e1b7e866e33fcbde78fe1746ec3a".to_owned(), + remove_date: DateTime::from_timestamp(1721999621, 0).unwrap(), + source: ChatBoostSource::Premium(ChatBoostSourcePremium { + user: User { + id: UserId(1459074222), + is_bot: false, + first_name: "shadowchain".to_owned(), + last_name: None, + username: Some("shdwchn10".to_owned()), + language_code: Some("en".to_owned()), + is_premium: true, + added_to_attachment_menu: false, + }, + }), + }), + }; + + let actual = serde_json::from_str::(json).unwrap(); + assert_eq!(expected, actual); + } } diff --git a/crates/teloxide-core/src/types/user_chat_boosts.rs b/crates/teloxide-core/src/types/user_chat_boosts.rs new file mode 100644 index 00000000..4b11bd4b --- /dev/null +++ b/crates/teloxide-core/src/types/user_chat_boosts.rs @@ -0,0 +1,43 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::ChatBoost; + +/// This object represents a list of boosts added to a chat by a user. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct UserChatBoosts { + /// The list of boosts added to the chat by the user. + pub boosts: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "boosts": [ + { + "boost_id": "4506e1b7e866e33fcbde78fe1746ec3a", + "add_date": 1721399621, + "expiration_date": 1745088963, + "source": { + "source": "premium", + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + } + } + } + ] + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/user_shared.rs b/crates/teloxide-core/src/types/user_shared.rs deleted file mode 100644 index 1fd0cefe..00000000 --- a/crates/teloxide-core/src/types/user_shared.rs +++ /dev/null @@ -1,15 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::UserId; - -/// Information about the chat whose identifier was shared with the bot using a -/// [`KeyboardButtonRequestUser`] button. -/// -/// [`KeyboardButtonRequestUser`]: crate::types::KeyboardButtonRequestUser -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub struct UserShared { - /// Identifier of the request. - pub request_id: i32, - /// Identifier of the shared user. - pub user_id: UserId, -} diff --git a/crates/teloxide-core/src/types/users_shared.rs b/crates/teloxide-core/src/types/users_shared.rs new file mode 100644 index 00000000..c78c3625 --- /dev/null +++ b/crates/teloxide-core/src/types/users_shared.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{RequestId, SharedUser}; + +/// This object contains information about the users whose identifiers were +/// shared with the bot using a [KeyboardButtonRequestUsers] button. +/// +/// [KeyboardButtonRequestUsers]: crate::types::KeyboardButtonRequestUsers +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct UsersShared { + /// Identifier of the request + pub request_id: RequestId, + /// Identifiers of the shared users + pub users: Vec, +} diff --git a/crates/teloxide-core/src/util.rs b/crates/teloxide-core/src/util.rs index a917428b..7fd8d533 100644 --- a/crates/teloxide-core/src/util.rs +++ b/crates/teloxide-core/src/util.rs @@ -44,6 +44,7 @@ pub(crate) fn mentioned_users_from_entities( | Email | PhoneNumber | Bold + | Blockquote | Italic | Underline | Strikethrough diff --git a/crates/teloxide-macros/CHANGELOG.md b/crates/teloxide-macros/CHANGELOG.md index 1f464682..aeb2e579 100644 --- a/crates/teloxide-macros/CHANGELOG.md +++ b/crates/teloxide-macros/CHANGELOG.md @@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## unreleased +### Changed + +- Environment bumps: ([#1147][pr1147]) + - MSRV (Minimal Supported Rust Version) was bumped from `1.70.0` to `1.80.0` + - Dependency `heck` was bumped to `0.5.0` + +[pr1147]: https://github.com/teloxide/teloxide/pull/1147 + +## 0.8.0 - 2024-08-16 + ### Added - Now you can use `#[command(command_separator="sep")]` (default is a whitespace character) to set the separator between command and its arguments ([issue #897](https://github.com/teloxide/teloxide/issues/897)) diff --git a/crates/teloxide-macros/Cargo.toml b/crates/teloxide-macros/Cargo.toml index 95cfb5a2..cb9dae3c 100644 --- a/crates/teloxide-macros/Cargo.toml +++ b/crates/teloxide-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teloxide-macros" -version = "0.7.1" +version = "0.8.0" description = "The teloxide's procedural macros" rust-version.workspace = true @@ -19,8 +19,8 @@ proc-macro = true [dependencies] quote = "1.0.7" proc-macro2 = "1.0.67" -syn = { version = "1.0.13", features = ["full"] } -heck = "0.4.0" +syn = { version = "1.0.13", features = ["full", "extra-traits"] } +heck = "0.5.0" [package.metadata.release] tag-prefix = "macros-" diff --git a/crates/teloxide/Cargo.toml b/crates/teloxide/Cargo.toml index 60d110f7..62c939db 100644 --- a/crates/teloxide/Cargo.toml +++ b/crates/teloxide/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teloxide" -version = "0.12.2" +version = "0.13.0" description = "An elegant Telegram bots framework for Rust" rust-version.workspace = true @@ -77,8 +77,9 @@ full = [ [dependencies] -teloxide-core = { version = "0.9.1", path = "../teloxide-core", default-features = false } -teloxide-macros = { version = "0.7.1", path = "../teloxide-macros", optional = true } +# replace me by the actual version when release, and return path when it's time to make 0-day fixes +teloxide-core = { path = "../../crates/teloxide-core", default-features = false } +teloxide-macros = { version = "0.8", optional = true } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } @@ -88,7 +89,7 @@ dptree = "0.3.0" # Uncomment this if you want to test teloxide with a specific dptree commit # dptree = { git = "https://github.com/teloxide/dptree", rev = "df578e4" } -tokio = { version = "1.8", features = ["fs"] } +tokio = { version = "1.39", features = ["fs"] } tokio-util = "0.7" tokio-stream = "0.1.8" @@ -104,7 +105,7 @@ pin-project = "1.0" aquamarine = "0.5.0" either = "1.9.0" -sqlx = { version = "0.7.3", optional = true, default-features = false, features = [ +sqlx = { version = "0.8.1", optional = true, default-features = false, features = [ "macros", "sqlite", "postgres" @@ -113,7 +114,7 @@ deadpool-redis = { version = "0.14", features = ["rt_tokio_1"], optional = true serde_cbor = { version = "0.11", optional = true } bincode = { version = "1.3", optional = true } axum = { version = "0.7.0", optional = true } -tower = { version = "0.4.13", optional = true } +tower = { version = "0.5.0", optional = true } tower-http = { version = "0.5.2", features = ["trace"], optional = true } rand = { version = "0.8.5", optional = true } @@ -122,8 +123,8 @@ rand = "0.8.3" pretty_env_logger = "0.5.0" serde = "1" serde_json = "1" -tokio = { version = "1.8", features = ["fs", "rt-multi-thread", "macros"] } -reqwest = "0.11.11" +tokio = { version = "1.39", features = ["fs", "rt-multi-thread", "macros"] } +reqwest = "0.12.7" chrono = "0.4" tokio-stream = "0.1" @@ -194,6 +195,10 @@ required-features = [ "macros", ] +[[example]] +name = "deep_linking" +required-features = ["macros", "ctrlc_handler"] + [[example]] name = "dialogue" required-features = ["macros", "ctrlc_handler"] diff --git a/crates/teloxide/examples/admin.rs b/crates/teloxide/examples/admin.rs index c54b7b67..a240a3bc 100644 --- a/crates/teloxide/examples/admin.rs +++ b/crates/teloxide/examples/admin.rs @@ -79,7 +79,7 @@ async fn kick_user(bot: Bot, msg: Message) -> ResponseResult<()> { match msg.reply_to_message() { Some(replied) => { // bot.unban_chat_member can also kicks a user from a group chat. - bot.unban_chat_member(msg.chat.id, replied.from().unwrap().id).await?; + bot.unban_chat_member(msg.chat.id, replied.from.as_ref().unwrap().id).await?; } None => { bot.send_message(msg.chat.id, "Use this command in reply to another message").await?; @@ -94,7 +94,7 @@ async fn ban_user(bot: Bot, msg: Message, time: Duration) -> ResponseResult<()> Some(replied) => { bot.kick_chat_member( msg.chat.id, - replied.from().expect("Must be MessageKind::Common").id, + replied.from.as_ref().expect("Must be MessageKind::Common").id, ) .until_date(msg.date + time) .await?; @@ -113,7 +113,7 @@ async fn mute_user(bot: Bot, msg: Message, time: Duration) -> ResponseResult<()> Some(replied) => { bot.restrict_chat_member( msg.chat.id, - replied.from().expect("Must be MessageKind::Common").id, + replied.from.as_ref().expect("Must be MessageKind::Common").id, ChatPermissions::empty(), ) .until_date(msg.date + time) diff --git a/crates/teloxide/examples/buttons.rs b/crates/teloxide/examples/buttons.rs index c640d708..daa538c2 100644 --- a/crates/teloxide/examples/buttons.rs +++ b/crates/teloxide/examples/buttons.rs @@ -116,8 +116,8 @@ async fn callback_handler(bot: Bot, q: CallbackQuery) -> Result<(), Box>; +pub type HandlerResult = Result<(), Box>; + +#[derive(Clone, PartialEq, Debug, Default)] +pub enum State { + #[default] + Start, + WriteToSomeone { + id: ChatId, + }, +} + +#[derive(BotCommands, Clone, Debug)] +#[command(rename_rule = "lowercase")] +pub enum StartCommand { + Start(String), +} + +#[tokio::main] +async fn main() { + pretty_env_logger::init(); + log::info!("Starting deep linking bot..."); + + let bot = Bot::from_env(); + + let handler = dialogue::enter::, State, _>() + .branch( + Update::filter_message() + .filter_command::() + .branch(case![StartCommand::Start(start)].endpoint(start)), + ) + .branch( + Update::filter_message() + .branch(case![State::WriteToSomeone { id }].endpoint(send_message)), + ); + + Dispatcher::builder(bot, handler) + .dependencies(deps![InMemStorage::::new()]) + .enable_ctrlc_handler() + .build() + .dispatch() + .await; +} + +pub async fn start( + bot: Bot, + dialogue: MyDialogue, + msg: Message, + start: String, // Available from `case![StartCommand::Start(start)]` + me: Me, +) -> HandlerResult { + if start.is_empty() { + // This means that it is just a regular link like https://t.me/some_bot, or a /start command + bot.send_message( + msg.chat.id, + format!( + "Hello!\n\nThis link allows anyone to message you secretly: {}?start={}", + me.tme_url(), + msg.chat.id + ), + ) + .await?; + dialogue.exit().await?; + } else { + // And this means that the link is like this: https://t.me/some_bot?start=123456789, + // or a /start 123456789 command + match start.parse::() { + Ok(id) => { + bot.send_message(msg.chat.id, "Send your message:").await?; + dialogue.update(State::WriteToSomeone { id: ChatId(id) }).await?; + } + Err(_) => { + bot.send_message(msg.chat.id, "Bad link!").await?; + dialogue.exit().await?; + } + } + } + Ok(()) +} + +pub async fn send_message( + bot: Bot, + id: ChatId, // Available from `State::WriteToSomeone` + msg: Message, + dialogue: MyDialogue, + me: Me, +) -> HandlerResult { + match msg.text() { + Some(text) => { + // Trying to send a message to the user + let sent_result = bot + .send_message(id, format!("You have a new message!\n\n{text}")) + .parse_mode(ParseMode::Html) + .await; + + // And if no error is returned, success! + if sent_result.is_ok() { + bot.send_message( + msg.chat.id, + format!( + "Message sent!\n\nYour link is: {}?start={}", + me.tme_url(), + msg.chat.id + ), + ) + .await?; + } else { + bot.send_message(msg.chat.id, "Error sending message! Maybe user blocked the bot?") + .await?; + } + dialogue.exit().await?; + } + None => { + bot.send_message(msg.chat.id, "This bot can send only text.").await?; + } + }; + Ok(()) +} diff --git a/crates/teloxide/examples/dispatching_features.rs b/crates/teloxide/examples/dispatching_features.rs index 1082c29f..bcc65a2e 100644 --- a/crates/teloxide/examples/dispatching_features.rs +++ b/crates/teloxide/examples/dispatching_features.rs @@ -3,7 +3,12 @@ use rand::Rng; -use teloxide::{prelude::*, types::Dice, utils::command::BotCommands}; +use teloxide::{ + dispatching::HandlerExt, + prelude::*, + types::{Dice, ReplyParameters}, + utils::command::BotCommands, +}; #[tokio::main] async fn main() { @@ -30,7 +35,7 @@ async fn main() { .branch( // Filter a maintainer by a user ID. dptree::filter(|cfg: ConfigParameters, msg: Message| { - msg.from().map(|user| user.id == cfg.bot_maintainer).unwrap_or_default() + msg.from.map(|user| user.id == cfg.bot_maintainer).unwrap_or_default() }) .filter_command::() .endpoint(|msg: Message, bot: Bot, cmd: MaintainerCommands| async move { @@ -48,19 +53,38 @@ async fn main() { .branch( // Filtering allow you to filter updates by some condition. dptree::filter(|msg: Message| msg.chat.is_group() || msg.chat.is_supergroup()) - // An endpoint is the last update handler. - .endpoint(|msg: Message, bot: Bot| async move { - log::info!("Received a message from a group chat."); - bot.send_message(msg.chat.id, "This is a group chat.").await?; - respond(()) - }), + .branch( + // Filtering by mention allows to filter only `/repeat@my_bot` commands. + // Use if you want to make sure that users refer specifically to your bot. + // Same as filter_command, the next handlers will receive a parsed + // `GroupCommand`. + dptree::entry().filter_mention_command::().endpoint( + |bot: Bot, msg: Message, cmd: GroupCommand| async move { + match cmd { + GroupCommand::Repeat { text } => { + bot.send_message(msg.chat.id, format!("You said: {text}")) + .await?; + Ok(()) + } + } + }, + ), + ) + .branch( + // An endpoint is the last update handler. + dptree::endpoint(|msg: Message, bot: Bot| async move { + log::info!("Received a message from a group chat."); + bot.send_message(msg.chat.id, "This is a group chat.").await?; + respond(()) + }), + ), ) .branch( // There are some extension filtering functions on `Message`. The following filter will // filter only messages with dices. Message::filter_dice().endpoint(|bot: Bot, msg: Message, dice: Dice| async move { bot.send_message(msg.chat.id, format!("Dice value: {}", dice.value)) - .reply_to_message_id(msg.id) + .reply_parameters(ReplyParameters::new(msg.id)) .await?; Ok(()) }), @@ -112,6 +136,14 @@ enum MaintainerCommands { Rand { from: u64, to: u64 }, } +/// Group commands +#[derive(BotCommands, Clone)] +#[command(rename_rule = "lowercase")] +enum GroupCommand { + /// Repeats a message + Repeat { text: String }, +} + async fn simple_commands_handler( cfg: ConfigParameters, bot: Bot, @@ -121,7 +153,7 @@ async fn simple_commands_handler( ) -> Result<(), teloxide::RequestError> { let text = match cmd { SimpleCommand::Help => { - if msg.from().unwrap().id == cfg.bot_maintainer { + if msg.from.unwrap().id == cfg.bot_maintainer { format!( "{}\n\n{}", SimpleCommand::descriptions(), @@ -134,7 +166,7 @@ async fn simple_commands_handler( } } SimpleCommand::Maintainer => { - if msg.from().unwrap().id == cfg.bot_maintainer { + if msg.from.as_ref().unwrap().id == cfg.bot_maintainer { "Maintainer is you!".into() } else if let Some(username) = cfg.maintainer_username { format!("Maintainer is @{username}") @@ -143,7 +175,7 @@ async fn simple_commands_handler( } } SimpleCommand::MyId => { - format!("{}", msg.from().unwrap().id) + format!("{}", msg.from.unwrap().id) } }; diff --git a/crates/teloxide/src/dispatching.rs b/crates/teloxide/src/dispatching.rs index 1caa197b..1d16b35a 100644 --- a/crates/teloxide/src/dispatching.rs +++ b/crates/teloxide/src/dispatching.rs @@ -227,4 +227,4 @@ pub use dispatcher::{Dispatcher, DispatcherBuilder, UpdateHandler}; pub use distribution::DefaultKey; pub use filter_ext::{MessageFilterExt, UpdateFilterExt}; pub use handler_description::DpHandlerDescription; -pub use handler_ext::{filter_command, HandlerExt}; +pub use handler_ext::{filter_command, filter_mention_command, HandlerExt}; diff --git a/crates/teloxide/src/dispatching/dialogue/get_chat_id.rs b/crates/teloxide/src/dispatching/dialogue/get_chat_id.rs index 632f4bbe..11fac664 100644 --- a/crates/teloxide/src/dispatching/dialogue/get_chat_id.rs +++ b/crates/teloxide/src/dispatching/dialogue/get_chat_id.rs @@ -16,7 +16,7 @@ impl GetChatId for Message { impl GetChatId for CallbackQuery { fn chat_id(&self) -> Option { - self.message.as_ref().map(|mes| mes.chat.id) + self.message.as_ref().map(|mes| mes.chat().id) } } diff --git a/crates/teloxide/src/dispatching/filter_ext.rs b/crates/teloxide/src/dispatching/filter_ext.rs index 532f614f..09b16eb6 100644 --- a/crates/teloxide/src/dispatching/filter_ext.rs +++ b/crates/teloxide/src/dispatching/filter_ext.rs @@ -1,4 +1,6 @@ #![allow(clippy::redundant_closure_call)] +// Required for the `filter_from` currently +#![allow(deprecated)] use dptree::{di::DependencyMap, Handler}; @@ -68,6 +70,7 @@ macro_rules! define_message_ext { } } +// FIXME: change macro so that we can filter things without getters // May be expanded in the future. define_message_ext! { // MessageCommon @@ -92,7 +95,8 @@ define_message_ext! { (filter_migration_from, Message::migrate_from_chat_id), (filter_migration_to, Message::migrate_to_chat_id), (filter_reply_to_message, Message::reply_to_message), - (filter_forward_from, Message::forward_from), + (filter_forward_origin, Message::forward_origin), + (filter_reply_to_story, Message::reply_to_story), // Rest variants of a MessageKind (filter_new_chat_members, Message::new_chat_members), (filter_left_chat_member, Message::left_chat_member), @@ -111,12 +115,17 @@ define_message_ext! { (filter_passport_data, Message::passport_data), (filter_dice, Message::dice), (filter_proximity_alert_triggered, Message::proximity_alert_triggered), + (filter_boost_added, Message::boost_added), (filter_forum_topic_created, Message::forum_topic_created), (filter_forum_topic_edited, Message::forum_topic_edited), (filter_forum_topic_closed, Message::forum_topic_closed), (filter_forum_topic_reopened, Message::forum_topic_reopened), (filter_general_forum_topic_hidden, Message::general_forum_topic_hidden), (filter_general_forum_topic_unhidden, Message::general_forum_topic_unhidden), + (filter_giveaway, Message::giveaway), + (filter_giveaway_completed, Message::giveaway_completed), + (filter_giveaway_created, Message::giveaway_created), + (filter_giveaway_winners, Message::giveaway_winners), (filter_video_chat_scheduled, Message::video_chat_scheduled), (filter_video_chat_started, Message::video_chat_started), (filter_video_chat_ended, Message::video_chat_ended), @@ -146,6 +155,12 @@ define_update_ext! { (filter_edited_message, UpdateKind::EditedMessage, EditedMessage), (filter_channel_post, UpdateKind::ChannelPost, ChannelPost), (filter_edited_channel_post, UpdateKind::EditedChannelPost, EditedChannelPost), + (filter_business_connection, UpdateKind::BusinessConnection, BusinessConnection), + (filter_business_message, UpdateKind::BusinessMessage, BusinessMessage), + (filter_edited_business_message, UpdateKind::EditedBusinessMessage, EditedBusinessMessage), + (filter_deleted_business_messages, UpdateKind::DeletedBusinessMessages, DeletedBusinessMessages), + (filter_message_reaction_updated, UpdateKind::MessageReaction, MessageReaction), + (filter_message_reaction_count_updated, UpdateKind::MessageReactionCount, MessageReactionCount), (filter_inline_query, UpdateKind::InlineQuery, InlineQuery), (filter_chosen_inline_result, UpdateKind::ChosenInlineResult, ChosenInlineResult), (filter_callback_query, UpdateKind::CallbackQuery, CallbackQuery), @@ -156,4 +171,6 @@ define_update_ext! { (filter_my_chat_member, UpdateKind::MyChatMember, MyChatMember), (filter_chat_member, UpdateKind::ChatMember, ChatMember), (filter_chat_join_request, UpdateKind::ChatJoinRequest, ChatJoinRequest), + (filter_chat_boost, UpdateKind::ChatBoost, ChatBoost), + (filter_removed_chat_boost, UpdateKind::RemovedChatBoost, RemovedChatBoost), } diff --git a/crates/teloxide/src/dispatching/handler_description.rs b/crates/teloxide/src/dispatching/handler_description.rs index eb49dda0..f798eee3 100644 --- a/crates/teloxide/src/dispatching/handler_description.rs +++ b/crates/teloxide/src/dispatching/handler_description.rs @@ -65,6 +65,12 @@ impl EventKind for Kind { EditedMessage, ChannelPost, EditedChannelPost, + BusinessConnection, + BusinessMessage, + EditedBusinessMessage, + DeletedBusinessMessages, + MessageReaction, + MessageReactionCount, InlineQuery, ChosenInlineResult, CallbackQuery, @@ -75,6 +81,8 @@ impl EventKind for Kind { MyChatMember, ChatMember, ChatJoinRequest, + ChatBoost, + RemovedChatBoost, ] .into_iter() .map(Kind) @@ -91,10 +99,12 @@ mod tests { #[cfg(feature = "macros")] use crate::{ self as teloxide, // fixup for the `BotCommands` macro - dispatching::{HandlerExt, UpdateFilterExt}, + dispatching::{handler_description::Kind, HandlerExt, UpdateFilterExt}, types::{AllowedUpdate::*, Update}, utils::command::BotCommands, }; + #[cfg(feature = "macros")] + use dptree::description::EventKind; #[cfg(feature = "macros")] #[derive(BotCommands, Clone)] @@ -128,4 +138,61 @@ mod tests { fn discussion_648() { panic!("this test requires `macros` feature") } + + // Test that all possible updates are specified in `Kind::full_set()` + #[test] + #[cfg(feature = "macros")] + fn allowed_updates_full_set() { + let full_set = Kind::full_set(); + let allowed_updates_reference = vec![ + Message, + EditedMessage, + ChannelPost, + EditedChannelPost, + MessageReaction, + MessageReactionCount, + InlineQuery, + ChosenInlineResult, + CallbackQuery, + ShippingQuery, + PreCheckoutQuery, + Poll, + PollAnswer, + MyChatMember, + ChatMember, + ChatJoinRequest, + ChatBoost, + RemovedChatBoost, + ]; + + for update in allowed_updates_reference { + match update { + // CAUTION: Don't forget to add new `UpdateKind` to `allowed_updates_reference`! + Message + | EditedMessage + | ChannelPost + | EditedChannelPost + | MessageReaction + | MessageReactionCount + | InlineQuery + | ChosenInlineResult + | CallbackQuery + | ShippingQuery + | PreCheckoutQuery + | Poll + | PollAnswer + | MyChatMember + | ChatMember + | ChatJoinRequest + | ChatBoost + | RemovedChatBoost + | BusinessMessage + | BusinessConnection + | EditedBusinessMessage + | DeletedBusinessMessages => { + assert!(full_set.contains(&Kind(update))) + } + } + } + } } diff --git a/crates/teloxide/src/dispatching/handler_ext.rs b/crates/teloxide/src/dispatching/handler_ext.rs index f5f3236d..d45c29f1 100644 --- a/crates/teloxide/src/dispatching/handler_ext.rs +++ b/crates/teloxide/src/dispatching/handler_ext.rs @@ -23,6 +23,18 @@ pub trait HandlerExt { where C: BotCommands + Send + Sync + 'static; + /// Returns a handler that accepts a parsed command `C` if the command + /// contains a bot mention, for example `/start@my_bot`. + /// + /// ## Dependency requirements + /// + /// - [`crate::types::Message`] + /// - [`crate::types::Me`] + #[must_use] + fn filter_mention_command(self) -> Self + where + C: BotCommands + Send + Sync + 'static; + /// Passes [`Dialogue`] and `D` as handler dependencies. /// /// It does so by the following steps: @@ -61,6 +73,13 @@ where self.chain(filter_command::()) } + fn filter_mention_command(self) -> Self + where + C: BotCommands + Send + Sync + 'static, + { + self.chain(filter_mention_command::()) + } + fn enter_dialogue(self) -> Self where S: Storage + ?Sized + Send + Sync + 'static, @@ -93,3 +112,192 @@ where message.text().and_then(|text| C::parse(text, &bot_name).ok()) }) } + +/// Returns a handler that accepts a parsed command `C` if the command +/// contains a bot mention, for example `/start@my_bot`. +/// +/// A call to this function is the same as +/// `dptree::entry().filter_mention_command()`. +/// +/// See [`HandlerExt::filter_mention_command`]. +/// +/// ## Dependency requirements +/// +/// - [`crate::types::Message`] +/// - [`crate::types::Me`] +#[must_use] +pub fn filter_mention_command( +) -> Handler<'static, DependencyMap, Output, DpHandlerDescription> +where + C: BotCommands + Send + Sync + 'static, + Output: Send + Sync + 'static, +{ + dptree::filter_map(move |message: Message, me: Me| { + let bot_name = me.user.username.expect("Bots must have a username"); + + let command = message.text().and_then(|text| C::parse(text, &bot_name).ok()); + // If the parsing succeeds with a bot_name, + // but fails without - there is a mention + let is_username_required = + message.text().and_then(|text| C::parse(text, "").ok()).is_none(); + + if !is_username_required { + return None; + } + command + }) +} + +#[cfg(test)] +#[cfg(feature = "macros")] +mod tests { + use crate::{self as teloxide, dispatching::UpdateFilterExt, utils::command::BotCommands}; + use chrono::DateTime; + use dptree::deps; + use teloxide_core::types::{ + Chat, ChatFullInfo, ChatId, ChatKind, ChatPrivate, LinkPreviewOptions, Me, MediaKind, + MediaText, Message, MessageCommon, MessageId, MessageKind, Update, UpdateId, UpdateKind, + User, UserId, + }; + + use super::HandlerExt; + + #[derive(BotCommands, Clone)] + #[command(rename_rule = "lowercase")] + enum Cmd { + Test, + } + + fn make_update(text: String) -> Update { + let timestamp = 1_569_518_829; + let date = DateTime::from_timestamp(timestamp, 0).unwrap(); + Update { + id: UpdateId(326_170_274), + kind: UpdateKind::Message(Message { + via_bot: None, + id: MessageId(5042), + thread_id: None, + from: Some(User { + id: UserId(109_998_024), + is_bot: false, + first_name: String::from("Laster"), + last_name: None, + username: Some(String::from("laster_alex")), + language_code: Some(String::from("en")), + is_premium: false, + added_to_attachment_menu: false, + }), + sender_chat: None, + is_topic_message: false, + sender_business_bot: None, + date, + chat: Chat { + id: ChatId(109_998_024), + kind: ChatKind::Private(ChatPrivate { + username: Some(String::from("Laster")), + first_name: Some(String::from("laster_alex")), + last_name: None, + bio: None, + has_private_forwards: None, + has_restricted_voice_and_video_messages: None, + business_intro: None, + business_location: None, + business_opening_hours: None, + birthdate: None, + personal_chat: None, + }), + photo: None, + available_reactions: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo::default(), + }, + kind: MessageKind::Common(MessageCommon { + reply_to_message: None, + forward_origin: None, + external_reply: None, + quote: None, + edit_date: None, + media_kind: MediaKind::Text(MediaText { + text, + entities: vec![], + link_preview_options: Some(LinkPreviewOptions { + is_disabled: true, + url: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, + }), + }), + reply_markup: None, + author_signature: None, + is_automatic_forward: false, + has_protected_content: false, + reply_to_story: None, + sender_boost_count: None, + is_from_offline: false, + business_connection_id: None, + }), + }), + } + } + + fn make_me() -> Me { + Me { + user: User { + id: UserId(42), + is_bot: true, + first_name: "First".to_owned(), + last_name: None, + username: Some("SomethingSomethingBot".to_owned()), + language_code: None, + is_premium: false, + added_to_attachment_menu: false, + }, + can_join_groups: false, + can_read_all_group_messages: false, + supports_inline_queries: false, + can_connect_to_business: false, + } + } + + #[tokio::test] + async fn test_filter_command() { + let h = dptree::entry() + .branch(Update::filter_message().filter_command::().endpoint(|| async {})); + let me = make_me(); + + let update = make_update("/test@".to_owned() + me.username()); + let result = h.dispatch(deps![update, me.clone()]).await; + assert!(result.is_break()); + + let update = make_update("/test@".to_owned() + "SomeOtherBot"); + let result = h.dispatch(deps![update, me.clone()]).await; + assert!(result.is_continue()); + + let update = make_update("/test".to_owned()); + let result = h.dispatch(deps![update, me.clone()]).await; + assert!(result.is_break()); + } + + #[tokio::test] + async fn test_filter_mention_command() { + let h = dptree::entry() + .branch(Update::filter_message().filter_mention_command::().endpoint(|| async {})); + let me = make_me(); + + let update = make_update("/test@".to_owned() + me.username()); + let result = h.dispatch(deps![update, me.clone()]).await; + assert!(result.is_break()); + + let update = make_update("/test@".to_owned() + "SomeOtherBot"); + let result = h.dispatch(deps![update, me.clone()]).await; + assert!(result.is_continue()); + + let update = make_update("/test".to_owned()); + let result = h.dispatch(deps![update, me.clone()]).await; + assert!(result.is_continue()); + } +} diff --git a/crates/teloxide/src/lib.rs b/crates/teloxide/src/lib.rs index 7be22d58..bcb901f1 100644 --- a/crates/teloxide/src/lib.rs +++ b/crates/teloxide/src/lib.rs @@ -1,6 +1,6 @@ //! A full-featured framework that empowers you to easily build [Telegram bots] //! using [Rust]. It handles all the difficult stuff so you can focus only on -//! your business logic. Currently, version `6.9` of [Telegram Bot API] is +//! your business logic. Currently, version `7.2` of [Telegram Bot API] is //! supported. //! //! For a high-level overview, see [our GitHub repository](https://github.com/teloxide/teloxide). @@ -149,7 +149,7 @@ pub use teloxide_core::*; #[cfg(feature = "macros")] pub use teloxide_macros as macros; -pub use dispatching::filter_command; +pub use dispatching::{filter_command, filter_mention_command}; pub use dptree::{self, case as handler}; #[cfg(all(feature = "nightly", doctest))] diff --git a/crates/teloxide/src/update_listeners/webhooks/axum.rs b/crates/teloxide/src/update_listeners/webhooks/axum.rs index 89f1cfb7..b925df5d 100644 --- a/crates/teloxide/src/update_listeners/webhooks/axum.rs +++ b/crates/teloxide/src/update_listeners/webhooks/axum.rs @@ -54,18 +54,12 @@ where tokio::spawn(async move { let tcp_listener = tokio::net::TcpListener::bind(address) .await - .map_err(|err| { - stop_token.stop(); - err - }) + .inspect_err(|_| stop_token.stop()) .expect("Couldn't bind to the address"); axum::serve(tcp_listener, app) .with_graceful_shutdown(stop_flag) .await - .map_err(|err| { - stop_token.stop(); - err - }) + .inspect_err(|_| stop_token.stop()) .expect("Axum server error"); }); diff --git a/crates/teloxide/src/utils/html.rs b/crates/teloxide/src/utils/html.rs index 5fa2ee91..823ecda1 100644 --- a/crates/teloxide/src/utils/html.rs +++ b/crates/teloxide/src/utils/html.rs @@ -14,6 +14,16 @@ pub fn bold(s: &str) -> String { format!("{s}") } +/// Applies the block quotation style to the string. +/// +/// Passed string will not be automatically escaped because it can contain +/// nested markup. +#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \ + without using its output does nothing useful"] +pub fn blockquote(s: &str) -> String { + format!("
{s}
") +} + /// Applies the italic font style to the string. /// /// Passed string will not be automatically escaped because it can contain diff --git a/crates/teloxide/src/utils/markdown.rs b/crates/teloxide/src/utils/markdown.rs index ad6eb26c..ec007c29 100644 --- a/crates/teloxide/src/utils/markdown.rs +++ b/crates/teloxide/src/utils/markdown.rs @@ -14,6 +14,16 @@ pub fn bold(s: &str) -> String { format!("*{s}*") } +/// Applies the block quotation style to the string. +/// +/// Passed string will not be automatically escaped because it can contain +/// nested markup. +#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \ + without using its output does nothing useful"] +pub fn blockquote(s: &str) -> String { + format!(">{s}") +} + /// Applies the italic font style to the string. /// /// Can be safely used with `utils::markdown::underline()`. diff --git a/crates/teloxide/src/utils/shutdown_token.rs b/crates/teloxide/src/utils/shutdown_token.rs index 14ca622f..f51cdd77 100644 --- a/crates/teloxide/src/utils/shutdown_token.rs +++ b/crates/teloxide/src/utils/shutdown_token.rs @@ -122,10 +122,7 @@ impl DispatcherState { .map(ShutdownState::from_u8) .map_err(ShutdownState::from_u8) // FIXME: `Result::inspect` when :( - .map(|st| { - self.notify.notify_waiters(); - st - }) + .inspect(|_| self.notify.notify_waiters()) } } diff --git a/triagebot.toml b/triagebot.toml index 54a675ea..9900272f 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -4,13 +4,10 @@ contributing_url = "https://github.com/teloxide/teloxide/blob/master/CONTRIBUTIN [assign.adhoc_groups] # This is a special group that will be used if none of the `owners` entries matches. -fallback = ["@WaffleLapkin", "@Hirrolot"] +fallback = ["@Hirrolot"] [assign.owners] -"crates/teloxide-core" = ["@WaffleLapkin"] -"crates/teloxide-macros" = ["@WaffleLapkin"] "crates/teloxide" = ["@Hirrolot"] -".github" = ["@WaffleLapkin"] [autolabel."S-waiting-on-review"]