diff --git a/CHANGELOG.md b/CHANGELOG.md
index 755a9793..8a2ed06c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - `ApiError::TooMuchInlineQueryResults` ([#135][pr135])
 - `ApiError::NotEnoughRightsToChangeChatPermissions` ([#155][pr155])
 - Support for 5.4 telegram bot API ([#133][pr133])
-- Support for 5.5 telegram bot API ([#143][pr143])
+- Support for 5.5 telegram bot API ([#143][pr143], [#164][pr164])
 - `EditedMessageIsTooLong` error ([#109][pr109])
 - `UntilDate` enum and use it for `{Restricted, Banned}::until_date` ([#116][pr116])
 - `Limits::messages_per_min_channel` ([#121][pr121])
@@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 [pr151]: https://github.com/teloxide/teloxide-core/pull/151
 [pr155]: https://github.com/teloxide/teloxide-core/pull/155
 [pr156]: https://github.com/teloxide/teloxide-core/pull/156
+[pr164]: https://github.com/teloxide/teloxide-core/pull/164
 
 ### Changed
 
@@ -45,11 +46,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - `InputFile` and related structures now do **not** implement `PartialEq`, `Eq` and `Hash` ([#133][pr133])
 - How forwarded messages are represented ([#151][pr151])
 - `RequestError::InvalidJson` now has a `raw` field with raw json for easier debugability ([#150][pr150])
+- `ChatPermissions` is now bitflags ([#157][pr157])
 
 [pr115]: https://github.com/teloxide/teloxide-core/pull/115
 [pr125]: https://github.com/teloxide/teloxide-core/pull/125
 [pr134]: https://github.com/teloxide/teloxide-core/pull/134
 [pr150]: https://github.com/teloxide/teloxide-core/pull/150
+[pr157]: https://github.com/teloxide/teloxide-core/pull/157
 
 ### Fixed
 
diff --git a/Cargo.toml b/Cargo.toml
index ad4bb0b8..baa83bfd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -42,9 +42,9 @@ once_cell = "1.5.0"
 never = "0.1.0"
 chrono = { version = "0.4.19", default-features = false }
 either = "1.6.1"
+bitflags = { version = "1.2" }
 
 vecrem = { version = "0.1", optional = true }
-bitflags = { version = "1.2", optional = true }
 
 [dev-dependencies]
 pretty_env_logger = "0.4"
@@ -66,7 +66,7 @@ nightly = []
 throttle = ["vecrem"]
 
 # Trace bot adaptor
-trace_adaptor = ["bitflags"]
+trace_adaptor = []
 
 # Erased bot adaptor
 erased = []
diff --git a/LICENSE b/LICENSE
index 84a1b0b0..3bc0c2bb 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2020 teloxide
+Copyright (c) 2020-2022 teloxide
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 186621c4..02d3faef 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
     <img src="https://img.shields.io/badge/license-MIT-blue.svg">
   </a>
   <a href="https://core.telegram.org/bots/api">
-    <img src="https://img.shields.io/badge/API%20coverage-Up%20to%205.3%20(inclusively)-green.svg">
+    <img src="https://img.shields.io/badge/API%20coverage-Up%20to%205.6%20(inclusively)-green.svg">
   </a>
   <a href="https://crates.io/crates/teloxide_core">
     <img src="https://img.shields.io/crates/v/teloxide_core.svg">
diff --git a/src/lib.rs b/src/lib.rs
index 13d8a213..dd7b59a1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -81,7 +81,7 @@
 // (see: `teloxide`). We can't use `docsrs` as it breaks tokio compilation in this case.
 #![cfg_attr(
     all(any(docsrs, dep_docsrs), feature = "nightly"),
-    feature(doc_cfg, doc_notable_trait)
+    feature(doc_cfg, doc_auto_cfg, doc_notable_trait)
 )]
 #![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
 #![cfg_attr(all(feature = "full", docsrs), deny(rustdoc::broken_intra_doc_links))]
diff --git a/src/payloads/copy_message.rs b/src/payloads/copy_message.rs
index b003fcf8..6eec57d5 100644
--- a/src/payloads/copy_message.rs
+++ b/src/payloads/copy_message.rs
@@ -37,6 +37,8 @@ impl_payload! {
             ///
             /// [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
             pub reply_to_message_id: i32,
             /// Pass _True_, if the message should be sent even if the specified replied-to message is not found
diff --git a/src/payloads/forward_message.rs b/src/payloads/forward_message.rs
index 2ccad51e..9c34f0b7 100644
--- a/src/payloads/forward_message.rs
+++ b/src/payloads/forward_message.rs
@@ -29,6 +29,8 @@ impl_payload! {
             ///
             /// [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/src/payloads/send_animation.rs b/src/payloads/send_animation.rs
index 6e05aa5b..e5c69e98 100644
--- a/src/payloads/send_animation.rs
+++ b/src/payloads/send_animation.rs
@@ -48,6 +48,8 @@ impl_payload! {
             ///
             /// [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
             pub reply_to_message_id: i32,
             /// Pass _True_, if the message should be sent even if the specified replied-to message is not found
diff --git a/src/payloads/send_audio.rs b/src/payloads/send_audio.rs
index 2097ccd3..61dd6c01 100644
--- a/src/payloads/send_audio.rs
+++ b/src/payloads/send_audio.rs
@@ -51,6 +51,8 @@ impl_payload! {
             ///
             /// [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
             pub reply_to_message_id: i32,
             /// Pass _True_, if the message should be sent even if the specified replied-to message is not found
diff --git a/src/payloads/send_contact.rs b/src/payloads/send_contact.rs
index 01852e8f..ab530447 100644
--- a/src/payloads/send_contact.rs
+++ b/src/payloads/send_contact.rs
@@ -35,6 +35,8 @@ impl_payload! {
             ///
             /// [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
             pub reply_to_message_id: i32,
             /// Pass _True_, if the message should be sent even if the specified replied-to message is not found
diff --git a/src/payloads/send_dice.rs b/src/payloads/send_dice.rs
index fc19d585..426154ac 100644
--- a/src/payloads/send_dice.rs
+++ b/src/payloads/send_dice.rs
@@ -27,6 +27,8 @@ impl_payload! {
             ///
             /// [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
             pub reply_to_message_id: i32,
             /// Pass _True_, if the message should be sent even if the specified replied-to message is not found
diff --git a/src/payloads/send_document.rs b/src/payloads/send_document.rs
index 3ca72ba4..71f5103a 100644
--- a/src/payloads/send_document.rs
+++ b/src/payloads/send_document.rs
@@ -44,6 +44,8 @@ impl_payload! {
             ///
             /// [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
             pub reply_to_message_id: i32,
             /// Pass _True_, if the message should be sent even if the specified replied-to message is not found
diff --git a/src/payloads/send_game.rs b/src/payloads/send_game.rs
index eaa8e9e9..8d30f29c 100644
--- a/src/payloads/send_game.rs
+++ b/src/payloads/send_game.rs
@@ -27,6 +27,8 @@ impl_payload! {
             ///
             /// [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
             pub reply_to_message_id: i32,
             /// Pass _True_, if the message should be sent even if the specified replied-to message is not found
diff --git a/src/payloads/send_invoice.rs b/src/payloads/send_invoice.rs
index 3ffe3b93..503ddfb6 100644
--- a/src/payloads/send_invoice.rs
+++ b/src/payloads/send_invoice.rs
@@ -72,6 +72,8 @@ impl_payload! {
             ///
             /// [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
             pub reply_to_message_id: i32,
             /// Pass _True_, if the message should be sent even if the specified replied-to message is not found
diff --git a/src/payloads/send_location.rs b/src/payloads/send_location.rs
index 96c7e4c4..ba482dc2 100644
--- a/src/payloads/send_location.rs
+++ b/src/payloads/send_location.rs
@@ -39,6 +39,8 @@ impl_payload! {
             ///
             /// [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
             pub reply_to_message_id: i32,
             /// Pass _True_, if the message should be sent even if the specified replied-to message is not found
diff --git a/src/payloads/send_media_group.rs b/src/payloads/send_media_group.rs
index 66d9f619..95fd7886 100644
--- a/src/payloads/send_media_group.rs
+++ b/src/payloads/send_media_group.rs
@@ -27,6 +27,8 @@ impl_payload! {
             ///
             /// [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
             pub reply_to_message_id: i32,
             /// Pass _True_, if the message should be sent even if the specified replied-to message is not found
diff --git a/src/payloads/send_message.rs b/src/payloads/send_message.rs
index e3ff3dab..e27ddfc1 100644
--- a/src/payloads/send_message.rs
+++ b/src/payloads/send_message.rs
@@ -35,6 +35,8 @@ impl_payload! {
             ///
             /// [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
             pub reply_to_message_id: i32,
             /// Pass _True_, if the message should be sent even if the specified replied-to message is not found
diff --git a/src/payloads/send_photo.rs b/src/payloads/send_photo.rs
index a5dffae2..d54e912d 100644
--- a/src/payloads/send_photo.rs
+++ b/src/payloads/send_photo.rs
@@ -38,6 +38,8 @@ impl_payload! {
             ///
             /// [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
             pub reply_to_message_id: i32,
             /// Pass _True_, if the message should be sent even if the specified replied-to message is not found
diff --git a/src/payloads/send_poll.rs b/src/payloads/send_poll.rs
index 88ecf6a8..52d19938 100644
--- a/src/payloads/send_poll.rs
+++ b/src/payloads/send_poll.rs
@@ -53,6 +53,8 @@ impl_payload! {
             ///
             /// [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
             pub reply_to_message_id: i32,
             /// Pass _True_, if the message should be sent even if the specified replied-to message is not found
diff --git a/src/payloads/send_sticker.rs b/src/payloads/send_sticker.rs
index 357e10c3..a66f4ba2 100644
--- a/src/payloads/send_sticker.rs
+++ b/src/payloads/send_sticker.rs
@@ -30,6 +30,8 @@ impl_payload! {
             ///
             /// [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
             pub reply_to_message_id: i32,
             /// Pass _True_, if the message should be sent even if the specified replied-to message is not found
diff --git a/src/payloads/send_venue.rs b/src/payloads/send_venue.rs
index b3181b78..78a901a2 100644
--- a/src/payloads/send_venue.rs
+++ b/src/payloads/send_venue.rs
@@ -43,6 +43,8 @@ impl_payload! {
             ///
             /// [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
             pub reply_to_message_id: i32,
             /// Pass _True_, if the message should be sent even if the specified replied-to message is not found
diff --git a/src/payloads/send_video.rs b/src/payloads/send_video.rs
index c815e255..0dae2154 100644
--- a/src/payloads/send_video.rs
+++ b/src/payloads/send_video.rs
@@ -51,6 +51,8 @@ impl_payload! {
             ///
             /// [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
             pub reply_to_message_id: i32,
             /// Pass _True_, if the message should be sent even if the specified replied-to message is not found
diff --git a/src/payloads/send_video_note.rs b/src/payloads/send_video_note.rs
index 417bc1ac..a8c69ce7 100644
--- a/src/payloads/send_video_note.rs
+++ b/src/payloads/send_video_note.rs
@@ -39,6 +39,8 @@ impl_payload! {
             ///
             /// [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
             pub reply_to_message_id: i32,
             /// Pass _True_, if the message should be sent even if the specified replied-to message is not found
diff --git a/src/serde_multipart/mod.rs b/src/serde_multipart/mod.rs
index 3eae3aa9..42ffa7f7 100644
--- a/src/serde_multipart/mod.rs
+++ b/src/serde_multipart/mod.rs
@@ -31,6 +31,7 @@ pub(crate) fn to_form<T: ?Sized + Serialize>(val: &T) -> impl Future<Output = Re
 }
 
 // https://github.com/teloxide/teloxide/issues/473
+#[cfg(test)]
 #[tokio::test]
 async fn issue_473() {
     use crate::{
diff --git a/src/types.rs b/src/types.rs
index d4ba801e..7c8f5a46 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -244,16 +244,11 @@ pub(crate) mod serde_opt_date_from_unix_timestamp {
             .map(|timestamp| DateTime::from_utc(NaiveDateTime::from_timestamp(timestamp, 0), Utc)))
     }
 
-    pub(crate) fn none<T>() -> Option<T> {
-        None
-    }
-
     #[test]
     fn test() {
         #[derive(Serialize, Deserialize)]
         struct Struct {
-            #[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")]
-            #[serde(default = "crate::types::serde_opt_date_from_unix_timestamp::none")]
+            #[serde(default, with = "crate::types::serde_opt_date_from_unix_timestamp")]
             date: Option<DateTime<Utc>>,
         }
 
diff --git a/src/types/chat.rs b/src/types/chat.rs
index e46f06ae..a3e6f3fa 100644
--- a/src/types/chat.rs
+++ b/src/types/chat.rs
@@ -70,6 +70,12 @@ pub struct ChatPublic {
     ///
     /// [`GetChat`]: crate::payloads::GetChat
     pub invite_link: Option<String>,
+
+    /// `True`, if messages from the chat can't be forwarded to other chats.
+    /// Returned only in [`GetChat`].
+    ///
+    /// [`GetChat`]: crate::payloads::GetChat
+    pub has_protected_content: Option<True>,
 }
 
 #[serde_with_macros::skip_serializing_none]
@@ -97,7 +103,7 @@ pub struct ChatPrivate {
     pub bio: Option<String>,
 
     /// `True`, if privacy settings of the other party in the private chat
-    /// allows to use tg://user?id=<user_id> links only in chats with the
+    /// allows to use `tg://user?id=<user_id>` links only in chats with the
     /// user. Returned only in [`GetChat`].
     ///
     /// [`GetChat`]: crate::payloads::GetChat
@@ -389,6 +395,17 @@ impl Chat {
         }
     }
 
+    /// `True`, if messages from the chat can't be forwarded to other chats.
+    /// Returned only in [`GetChat`].
+    ///
+    /// [`GetChat`]: crate::payloads::GetChat
+    pub fn has_protected_content(&self) -> Option<True> {
+        match &self.kind {
+            ChatKind::Public(this) => this.has_protected_content,
+            _ => None,
+        }
+    }
+
     /// A first name of the other party in a private chat.
     pub fn first_name(&self) -> Option<&str> {
         match &self.kind {
@@ -446,6 +463,7 @@ mod tests {
                 }),
                 description: None,
                 invite_link: None,
+                has_protected_content: None,
             }),
             photo: None,
             pinned_message: None,
@@ -466,7 +484,7 @@ mod tests {
                     first_name: Some("Anon".into()),
                     last_name: None,
                     bio: None,
-                    has_private_forwards: None
+                    has_private_forwards: None,
                 }),
                 photo: None,
                 pinned_message: None,
diff --git a/src/types/chat_invite_link.rs b/src/types/chat_invite_link.rs
index 10ed8ac0..077d5d76 100644
--- a/src/types/chat_invite_link.rs
+++ b/src/types/chat_invite_link.rs
@@ -21,8 +21,7 @@ pub struct ChatInviteLink {
     pub name: Option<String>,
     /// Point in time when the link will expire or has been
     /// expired
-    #[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")]
-    #[serde(default = "crate::types::serde_opt_date_from_unix_timestamp::none")]
+    #[serde(default, with = "crate::types::serde_opt_date_from_unix_timestamp")]
     pub expire_date: Option<DateTime<Utc>>,
     /// Maximum number of users that can be members of the chat simultaneously
     /// after joining the chat via this invite link; 1-99999
diff --git a/src/types/chat_permissions.rs b/src/types/chat_permissions.rs
index d95b6643..77585d70 100644
--- a/src/types/chat_permissions.rs
+++ b/src/types/chat_permissions.rs
@@ -1,96 +1,192 @@
 use serde::{Deserialize, Serialize};
+use std::ops::Not;
 
-/// Describes actions that a non-administrator user is allowed to take in a
-/// chat.
-///
-/// [The official docs](https://core.telegram.org/bots/api#chatpermissions).
-#[serde_with_macros::skip_serializing_none]
-#[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
-pub struct ChatPermissions {
-    /// `true`, if the user is allowed to send text messages, contacts,
-    /// locations and venues.
-    pub can_send_messages: Option<bool>,
+bitflags::bitflags! {
+    /// Describes actions that a non-administrator user is allowed to take in a
+    /// chat.
+    ///
+    /// [The official docs](https://core.telegram.org/bots/api#chatpermissions).
+    ///
+    /// ## Examples
+    ///
+    /// ```
+    /// use teloxide_core::types::ChatPermissions;
+    ///
+    /// // No permissions, nothing is allowed
+    /// let _ = ChatPermissions::empty();
+    ///
+    /// // All permissions, everything is allowed
+    /// let _ = ChatPermissions::all();
+    ///
+    /// // One particular permission
+    /// let permissions_v0 = ChatPermissions::INVITE_USERS;
+    ///
+    /// // Check what is permitted
+    /// assert!(permissions_v0.contains(ChatPermissions::INVITE_USERS));
+    /// assert!(!permissions_v0.contains(ChatPermissions::SEND_MESSAGES));
+    ///
+    /// // Union, add permissions
+    /// let permissions_v1 = permissions_v0 | ChatPermissions::SEND_MEDIA_MESSAGES;
+    /// assert!(permissions_v1.contains(ChatPermissions::INVITE_USERS));
+    /// assert!(permissions_v1.contains(ChatPermissions::SEND_MEDIA_MESSAGES));
+    ///
+    /// // Implied by `SEND_MEDIA_MESSAGES`
+    /// assert!(permissions_v1.contains(ChatPermissions::SEND_MESSAGES));
+    ///
+    /// // Difference, remove permissions
+    /// let permissions_v2 = permissions_v1 - ChatPermissions::SEND_MEDIA_MESSAGES;
+    /// assert!(!permissions_v2.contains(ChatPermissions::SEND_MEDIA_MESSAGES));
+    ///
+    /// // Removing `SEND_MEDIA_MESSAGES` also removes `SEND_MESSAGES` and vice versa
+    /// // because `SEND_MESSAGES` is implied by `SEND_MEDIA_MESSAGES`
+    /// assert!(!permissions_v2.contains(ChatPermissions::SEND_MESSAGES));
+    ///
+    /// let permissions_v3 = permissions_v1 - ChatPermissions::SEND_MESSAGES;
+    /// assert!(!permissions_v3.contains(ChatPermissions::SEND_MEDIA_MESSAGES));
+    /// ```
+    #[derive(Serialize, Deserialize)]
+    #[serde(from = "ChatPermissionsRaw", into = "ChatPermissionsRaw")]
+    pub struct ChatPermissions: u8 {
+        /// Set if the user is allowed to send text messages, contacts,
+        /// locations and venues.
+        const SEND_MESSAGES = 1;
 
-    /// `true`, if the user is allowed to send audios, documents,
-    /// photos, videos, video notes and voice notes, implies
-    /// `can_send_messages`.
-    pub can_send_media_messages: Option<bool>,
+        /// Set if the user is allowed to send audios, documents,
+        /// photos, videos, video notes and voice notes, implies
+        /// `SEND_MESSAGES`.
+        const SEND_MEDIA_MESSAGES = (1 << 1) | Self::SEND_MESSAGES.bits;
 
-    /// `true`, if the user is allowed to send polls, implies
-    /// `can_send_messages`.
-    pub can_send_polls: Option<bool>,
+        /// Set if the user is allowed to send polls, implies
+        /// `SEND_MESSAGES`.
+        const SEND_POLLS = (1 << 2) | Self::SEND_MESSAGES.bits;
 
-    /// `true`, if the user is allowed to send animations, games, stickers and
-    /// use inline bots, implies `can_send_media_messages`.
-    pub can_send_other_messages: Option<bool>,
+        /// Set if the user is allowed to send animations, games, stickers and
+        /// use inline bots, implies `SEND_MEDIA_MESSAGES`.
+        const SEND_OTHER_MESSAGES = (1 << 3) | Self::SEND_MEDIA_MESSAGES.bits;
 
-    /// `true`, if the user is allowed to add web page previews to
-    /// their messages, implies `can_send_media_messages`.
-    pub can_add_web_page_previews: Option<bool>,
+        /// Set if the user is allowed to add web page previews to
+        /// their messages, implies `SEND_MEDIA_MESSAGES`.
+        const ADD_WEB_PAGE_PREVIEWS = (1 << 4) | Self::SEND_MEDIA_MESSAGES.bits;
 
-    /// `true`, if the user is allowed to change the chat title, photo and
-    /// other settings. Ignored in public supergroups.
-    pub can_change_info: Option<bool>,
+        /// Set if the user is allowed to change the chat title, photo and
+        /// other settings. Ignored in public supergroups.
+        const CHANGE_INFO = (1 << 5);
 
-    /// `true`, if the user is allowed to invite new users to the chat.
-    pub can_invite_users: Option<bool>,
+        /// Set if the user is allowed to invite new users to the chat.
+        const INVITE_USERS = (1 << 6);
 
-    /// `true`, if the user is allowed to pin messages. Ignored in public
-    /// supergroups.
-    pub can_pin_messages: Option<bool>,
+        /// Set if the user is allowed to pin messages. Ignored in public
+        /// supergroups.
+        const PIN_MESSAGES = (1 << 7);
+    }
 }
 
-impl ChatPermissions {
-    pub const fn new() -> Self {
+/// Helper for (de)serialization
+#[derive(Serialize, Deserialize)]
+struct ChatPermissionsRaw {
+    #[serde(default, skip_serializing_if = "Not::not")]
+    can_send_messages: bool,
+
+    #[serde(default, skip_serializing_if = "Not::not")]
+    can_send_media_messages: bool,
+
+    #[serde(default, skip_serializing_if = "Not::not")]
+    can_send_polls: bool,
+
+    #[serde(default, skip_serializing_if = "Not::not")]
+    can_send_other_messages: bool,
+
+    #[serde(default, skip_serializing_if = "Not::not")]
+    can_add_web_page_previews: bool,
+
+    #[serde(default, skip_serializing_if = "Not::not")]
+    can_change_info: bool,
+
+    #[serde(default, skip_serializing_if = "Not::not")]
+    can_invite_users: bool,
+
+    #[serde(default, skip_serializing_if = "Not::not")]
+    can_pin_messages: bool,
+}
+
+impl From<ChatPermissions> for ChatPermissionsRaw {
+    fn from(this: ChatPermissions) -> Self {
         Self {
-            can_send_messages: None,
-            can_send_media_messages: None,
-            can_send_polls: None,
-            can_send_other_messages: None,
-            can_add_web_page_previews: None,
-            can_change_info: None,
-            can_invite_users: None,
-            can_pin_messages: None,
+            can_send_messages: this.contains(ChatPermissions::SEND_MESSAGES),
+            can_send_media_messages: this.contains(ChatPermissions::SEND_MEDIA_MESSAGES),
+            can_send_polls: this.contains(ChatPermissions::SEND_POLLS),
+            can_send_other_messages: this.contains(ChatPermissions::SEND_OTHER_MESSAGES),
+            can_add_web_page_previews: this.contains(ChatPermissions::ADD_WEB_PAGE_PREVIEWS),
+            can_change_info: this.contains(ChatPermissions::CHANGE_INFO),
+            can_invite_users: this.contains(ChatPermissions::INVITE_USERS),
+            can_pin_messages: this.contains(ChatPermissions::PIN_MESSAGES),
         }
     }
+}
 
-    pub const fn can_send_messages(mut self, val: bool) -> Self {
-        self.can_send_messages = Some(val);
-        self
-    }
+impl From<ChatPermissionsRaw> for ChatPermissions {
+    fn from(
+        ChatPermissionsRaw {
+            can_send_messages,
+            can_send_media_messages,
+            can_send_polls,
+            can_send_other_messages,
+            can_add_web_page_previews,
+            can_change_info,
+            can_invite_users,
+            can_pin_messages,
+        }: ChatPermissionsRaw,
+    ) -> Self {
+        let mut this = Self::empty();
 
-    pub const fn can_send_media_messages(mut self, val: bool) -> Self {
-        self.can_send_media_messages = Some(val);
-        self
-    }
+        if can_send_messages {
+            this |= Self::SEND_MESSAGES;
+        }
+        if can_send_media_messages {
+            this |= Self::SEND_MEDIA_MESSAGES
+        }
+        if can_send_polls {
+            this |= Self::SEND_POLLS;
+        }
+        if can_send_other_messages {
+            this |= Self::SEND_OTHER_MESSAGES;
+        }
+        if can_add_web_page_previews {
+            this |= Self::ADD_WEB_PAGE_PREVIEWS;
+        }
+        if can_change_info {
+            this |= Self::CHANGE_INFO;
+        }
+        if can_invite_users {
+            this |= Self::INVITE_USERS;
+        }
+        if can_pin_messages {
+            this |= Self::PIN_MESSAGES;
+        }
 
-    pub const fn can_send_polls(mut self, val: bool) -> Self {
-        self.can_send_polls = Some(val);
-        self
-    }
-
-    pub const fn can_send_other_messages(mut self, val: bool) -> Self {
-        self.can_send_other_messages = Some(val);
-        self
-    }
-
-    pub const fn can_add_web_page_previews(mut self, val: bool) -> Self {
-        self.can_add_web_page_previews = Some(val);
-        self
-    }
-
-    pub const fn can_change_info(mut self, val: bool) -> Self {
-        self.can_change_info = Some(val);
-        self
-    }
-
-    pub const fn can_invite_users(mut self, val: bool) -> Self {
-        self.can_invite_users = Some(val);
-        self
-    }
-
-    pub const fn can_pin_messages(mut self, val: bool) -> Self {
-        self.can_pin_messages = Some(val);
-        self
+        this
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::ChatPermissions;
+
+    #[test]
+    fn serialization() {
+        let permissions = ChatPermissions::SEND_MEDIA_MESSAGES | ChatPermissions::PIN_MESSAGES;
+        let expected =
+            r#"{"can_send_messages":true,"can_send_media_messages":true,"can_pin_messages":true}"#;
+        let actual = serde_json::to_string(&permissions).unwrap();
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn deserialization() {
+        let json =
+            r#"{"can_send_messages":true,"can_send_media_messages":true,"can_pin_messages":true}"#;
+        let expected = ChatPermissions::SEND_MEDIA_MESSAGES | ChatPermissions::PIN_MESSAGES;
+        let actual = serde_json::from_str(json).unwrap();
+        assert_eq!(expected, actual);
     }
 }
diff --git a/src/types/document.rs b/src/types/document.rs
index 3ea3c861..c3b51792 100644
--- a/src/types/document.rs
+++ b/src/types/document.rs
@@ -29,7 +29,7 @@ pub struct Document {
     pub file_name: Option<String>,
 
     /// A MIME type of the file as defined by a sender.
-    #[serde(with = "crate::types::non_telegram_types::mime::opt_deser")]
+    #[serde(default, with = "crate::types::non_telegram_types::mime::opt_deser")]
     pub mime_type: Option<Mime>,
 
     /// A size of a file.
diff --git a/src/types/message.rs b/src/types/message.rs
index 95a58ea4..7405c7ad 100644
--- a/src/types/message.rs
+++ b/src/types/message.rs
@@ -85,8 +85,7 @@ pub struct MessageCommon {
     pub reply_to_message: Option<Box<Message>>,
 
     /// Date the message was last edited in Unix time.
-    #[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")]
-    #[serde(default = "crate::types::serde_opt_date_from_unix_timestamp::none")]
+    #[serde(default, with = "crate::types::serde_opt_date_from_unix_timestamp")]
     pub edit_date: Option<DateTime<Utc>>,
 
     #[serde(flatten)]
@@ -363,7 +362,7 @@ pub struct MediaDocument {
 
     /// For messages with a caption, special entities like usernames, URLs,
     /// bot commands, etc. that appear in the caption.
-    #[serde(default = "Vec::new")]
+    #[serde(default)]
     pub caption_entities: Vec<MessageEntity>,
 
     /// The unique identifier of a media message group this message belongs
@@ -1340,10 +1339,11 @@ mod tests {
                 }),
                 description: None,
                 invite_link: None,
+                has_protected_content: None,
             }),
+            message_auto_delete_time: None,
             photo: None,
             pinned_message: None,
-            message_auto_delete_time: None,
         };
 
         assert!(message.from().unwrap().is_anonymous());
diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs
index cd8b6278..7fe19381 100644
--- a/src/types/message_entity.rs
+++ b/src/types/message_entity.rs
@@ -64,6 +64,7 @@ pub enum MessageEntityKind {
     TextMention { user: User },
     Underline,
     Strikethrough,
+    Spoiler,
 }
 
 #[cfg(test)]
diff --git a/src/types/parse_mode.rs b/src/types/parse_mode.rs
index 88a42c48..edd652fe 100644
--- a/src/types/parse_mode.rs
+++ b/src/types/parse_mode.rs
@@ -12,13 +12,20 @@ use serde::{Deserialize, Serialize};
 /// Formatting options.
 ///
 /// The Bot API supports basic formatting for messages. You can use bold,
-/// italic, underlined and strikethrough text, as well as inline links and
-/// pre-formatted code in your bots' messages. Telegram clients will render
+/// italic, underlined, strikethrough, and spoiler text, as well as inline links
+/// and pre-formatted code in your bots' messages. Telegram clients will render
 /// them accordingly. You can use either markdown-style or HTML-style
 /// formatting.
 ///
 /// Note that Telegram clients will display an **alert** to the user before
-/// opening an inline link (‘Open this link?’ together with the full URL).
+/// opening an inline link ('Open this link?' together with the full URL).
+///
+/// Message entities can be nested, providing following restrictions are met:
+/// - If two entities have common characters then one of them is fully contained
+///   inside another.
+/// - bold, italic, underline, strikethrough, and spoiler entities can contain
+///   and can be part of any other entities, except pre and code.
+/// - All other entities can't contain each other.
 ///
 /// Links `tg://user?id=<user_id>` can be used to mention a user by their ID
 /// without using a username. Please note:
@@ -39,7 +46,8 @@ use serde::{Deserialize, Serialize};
 /// _italic \*text_
 /// __underline__
 /// ~strikethrough~
-/// *bold _italic bold ~italic bold strikethrough~ __underline italic bold___ bold*
+/// ||spoiler||
+/// *bold _italic bold ~italic bold strikethrough ||italic bold strikethrough spoiler||~ __underline italic bold___ bold*
 /// [inline URL](http://www.example.com/)
 /// [inline mention of a user](tg://user?id=123456789)
 /// `inline fixed-width code`
@@ -77,9 +85,10 @@ use serde::{Deserialize, Serialize};
 /// <i>italic</i>, <em>italic</em>
 /// <u>underline</u>, <ins>underline</ins>
 /// <s>strikethrough</s>, <strike>strikethrough</strike>, <del>strikethrough</del>
-/// <b>bold <i>italic bold <s>italic bold strikethrough</s> <u>underline italic bold</u></i> bold</b>
-/// <a href="http:// www.example.com/">inline URL</a>
-/// <a href="tg:// user?id=123456789">inline mention of a user</a>
+/// <span class="tg-spoiler">spoiler</span>, <tg-spoiler>spoiler</tg-spoiler>
+/// <b>bold <i>italic bold <s>italic bold strikethrough <span class="tg-spoiler">italic bold strikethrough spoiler</span></s> <u>underline italic bold</u></i> bold</b>
+/// <a href="http://www.example.com/">inline URL</a>
+/// <a href="tg://user?id=123456789">inline mention of a user</a>
 /// <code>inline fixed-width code</code>
 /// <pre>pre-formatted fixed-width code block</pre>
 #[doc = "<pre><code class=\"language-rust\">pre-formatted fixed-width code block written in the \
diff --git a/src/types/poll.rs b/src/types/poll.rs
index a0946ca5..4a35a7d5 100644
--- a/src/types/poll.rs
+++ b/src/types/poll.rs
@@ -51,8 +51,7 @@ pub struct Poll {
     pub open_period: Option<u16>,
 
     /// Point in time when the poll will be automatically closed.
-    #[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")]
-    #[serde(default = "crate::types::serde_opt_date_from_unix_timestamp::none")]
+    #[serde(default, with = "crate::types::serde_opt_date_from_unix_timestamp")]
     pub close_date: Option<DateTime<Utc>>,
 }
 
diff --git a/src/types/webhook_info.rs b/src/types/webhook_info.rs
index 6a96beb1..f90e2a53 100644
--- a/src/types/webhook_info.rs
+++ b/src/types/webhook_info.rs
@@ -22,8 +22,7 @@ pub struct WebhookInfo {
 
     /// Time of the most recent error that happened when trying to
     /// deliver an update via webhook.
-    #[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")]
-    #[serde(default = "crate::types::serde_opt_date_from_unix_timestamp::none")]
+    #[serde(default, with = "crate::types::serde_opt_date_from_unix_timestamp")]
     pub last_error_date: Option<DateTime<Utc>>,
 
     /// Error message in human-readable format for the most recent error that